diff --git a/.github/funding.yml b/.github/funding.yml
new file mode 100644
index 00000000..ae4a88c0
--- /dev/null
+++ b/.github/funding.yml
@@ -0,0 +1 @@
+ko_fi: lysec
\ No newline at end of file
diff --git a/.zed/settings.json b/.zed/settings.json
deleted file mode 100644
index 5f7d4f63..00000000
--- a/.zed/settings.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "languages": {
- "QML": {
- "format_on_save": "on",
- "formatter": {
- "external": {
- "command": "qmlfmt",
- "arguments": ["-e", "-b", "360", "-t", "2", "-i", "2"]
- }
- }
- }
- }
-}
diff --git a/Assets/ColorScheme/Ayu/terminal/foot/Ayu-dark b/Assets/ColorScheme/Ayu/terminal/foot/Ayu-dark
index 9b2c9351..db50321a 100644
--- a/Assets/ColorScheme/Ayu/terminal/foot/Ayu-dark
+++ b/Assets/ColorScheme/Ayu/terminal/foot/Ayu-dark
@@ -1,6 +1,3 @@
-[cursor]
-color=1f2430 ffcc66
-
[colors]
foreground=cccac2
background=1f2430
@@ -21,4 +18,5 @@ bright5=dfbfff
bright6=95e6cb
bright7=ffffff
selection-foreground=1f2430
-selection-background=409fff
\ No newline at end of file
+selection-background=409fff
+cursor=1f2430 ffcc66
diff --git a/Assets/ColorScheme/Ayu/terminal/foot/Ayu-light b/Assets/ColorScheme/Ayu/terminal/foot/Ayu-light
index 4b3a615c..0686b00b 100644
--- a/Assets/ColorScheme/Ayu/terminal/foot/Ayu-light
+++ b/Assets/ColorScheme/Ayu/terminal/foot/Ayu-light
@@ -1,6 +1,3 @@
-[cursor]
-color=f8f9fa ffaa33
-
[colors]
foreground=5c6166
background=f8f9fa
@@ -22,3 +19,4 @@ bright6=4cbf99
bright7=d1d1d1
selection-foreground=f8f9fa
selection-background=035bd6
+cursor=f8f9fa ffaa33
diff --git a/Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-dark b/Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-dark
index 593188f9..6e30e18c 100644
--- a/Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-dark
+++ b/Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-dark
@@ -1,6 +1,3 @@
-[cursor]
-color=1e1e2e f5e0dc
-
[colors]
foreground=cdd6f4
background=1e1e2e
@@ -22,3 +19,4 @@ bright6=6bd7ca
bright7=bac2de
selection-foreground=cdd6f4
selection-background=585b70
+cursor=1e1e2e f5e0dc
diff --git a/Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-light b/Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-light
index afe3025b..6877ab82 100644
--- a/Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-light
+++ b/Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-light
@@ -1,6 +1,3 @@
-[cursor]
-color=303446 f2d5cf
-
[colors]
foreground=c6d0f5
background=303446
@@ -22,3 +19,4 @@ bright6=5abfb5
bright7=b5bfe2
selection-foreground=c6d0f5
selection-background=626880
+cursor=303446 f2d5cf
diff --git a/Assets/ColorScheme/Dracula/terminal/foot/Dracula-dark b/Assets/ColorScheme/Dracula/terminal/foot/Dracula-dark
index 56d26b00..64ead8a4 100644
--- a/Assets/ColorScheme/Dracula/terminal/foot/Dracula-dark
+++ b/Assets/ColorScheme/Dracula/terminal/foot/Dracula-dark
@@ -1,6 +1,3 @@
-[cursor]
-color=282a36 f8f8f2
-
[colors]
foreground=f8f8f2
background=282a36
@@ -21,4 +18,5 @@ bright5=ff92df
bright6=a4ffff
bright7=ffffff
selection-foreground=ffffff
-selection-background=44475a
\ No newline at end of file
+selection-background=44475a
+cursor=282a36 f8f8f2
diff --git a/Assets/ColorScheme/Dracula/terminal/foot/Dracula-light b/Assets/ColorScheme/Dracula/terminal/foot/Dracula-light
index 92d94f63..a8214cec 100644
--- a/Assets/ColorScheme/Dracula/terminal/foot/Dracula-light
+++ b/Assets/ColorScheme/Dracula/terminal/foot/Dracula-light
@@ -1,6 +1,3 @@
-[cursor]
-color=ffffff 282a36
-
[colors]
foreground=282a36
background=ffffff
@@ -22,3 +19,4 @@ bright6=a4ffff
bright7=000000
selection-foreground=ffffff
selection-background=6272a4
+cursor=ffffff 282a36
diff --git a/Assets/ColorScheme/Everforest/terminal/foot/Everforest-dark b/Assets/ColorScheme/Everforest/terminal/foot/Everforest-dark
index 5d34d399..f049a598 100644
--- a/Assets/ColorScheme/Everforest/terminal/foot/Everforest-dark
+++ b/Assets/ColorScheme/Everforest/terminal/foot/Everforest-dark
@@ -1,6 +1,3 @@
-[cursor]
-color=4c3743 e69875
-
[colors]
foreground=d3c6aa
background=1e2326
@@ -21,4 +18,5 @@ bright5=df69ba
bright6=35a77c
bright7=fffbef
selection-foreground=d3c6aa
-selection-background=4c3743
\ No newline at end of file
+selection-background=4c3743
+cursor=4c3743 e69875
diff --git a/Assets/ColorScheme/Everforest/terminal/foot/Everforest-light b/Assets/ColorScheme/Everforest/terminal/foot/Everforest-light
index 56721a1e..c1af0ad7 100644
--- a/Assets/ColorScheme/Everforest/terminal/foot/Everforest-light
+++ b/Assets/ColorScheme/Everforest/terminal/foot/Everforest-light
@@ -1,6 +1,3 @@
-[cursor]
-color=eaedc8 f57d26
-
[colors]
foreground=5c6a72
background=efebd4
@@ -21,4 +18,5 @@ bright5=df69ba
bright6=35a77c
bright7=fffbef
selection-foreground=5c6a72
-selection-background=eaedc8
\ No newline at end of file
+selection-background=eaedc8
+cursor=eaedc8 f57d26
diff --git a/Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-dark b/Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-dark
index ca3205bc..aee8ef7f 100644
--- a/Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-dark
+++ b/Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-dark
@@ -1,7 +1,4 @@
-[cursor]
-color=282828 ebdbb2
-
[colors]
foreground=ebdbb2
background=282828
@@ -22,4 +19,5 @@ bright5=d3869b
bright6=8ec07c
bright7=ebdbb2
selection-foreground=ebdbb2
-selection-background=665c54
\ No newline at end of file
+selection-background=665c54
+cursor=282828 ebdbb2
diff --git a/Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-light b/Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-light
index 8943a299..94bda7a7 100644
--- a/Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-light
+++ b/Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-light
@@ -1,7 +1,4 @@
-[cursor]
-color=625e5c 3c3836
-
[colors]
foreground=3c3836
background=fbf1c7
@@ -22,4 +19,5 @@ bright5=8f3f71
bright6=427b58
bright7=3c3836
selection-foreground=fbf1c7
-selection-background=3c3836
\ No newline at end of file
+selection-background=3c3836
+cursor=625e5c 3c3836
diff --git a/Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-dark b/Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-dark
index e8b46621..fbfe1f69 100644
--- a/Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-dark
+++ b/Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-dark
@@ -1,6 +1,3 @@
-[cursor]
-color=1f1f28 e6e0c2
-
[colors]
foreground=ddd8bb
background=1f1f28
@@ -21,4 +18,5 @@ bright5=a98fd2
bright6=7bc2df
bright7=a8a48d
selection-foreground=ddd8bb
-selection-background=49473e
\ No newline at end of file
+selection-background=49473e
+cursor=1f1f28 e6e0c2
diff --git a/Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-light b/Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-light
index e852a960..b3712eee 100644
--- a/Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-light
+++ b/Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-light
@@ -1,6 +1,3 @@
-[cursor]
-color=f2ecbc 43436c
-
[colors]
foreground=545464
background=f2ecbc
@@ -22,3 +19,4 @@ bright6=5e857a
bright7=43436c
selection-foreground=f2ecbc
selection-background=c9cbd1
+cursor=f2ecbc 43436c
diff --git a/Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-dark b/Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-dark
index 7f5b2ccf..08182714 100644
--- a/Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-dark
+++ b/Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-dark
@@ -1,6 +1,3 @@
-[cursor]
-color=111111 aaaaaa
-
[colors]
foreground=828282
background=111111
@@ -22,3 +19,4 @@ bright6=cccccc
bright7=ffffff
selection-foreground=111111
selection-background=828282
+cursor=111111 aaaaaa
diff --git a/Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-light b/Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-light
index 479082cb..bf57181b 100644
--- a/Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-light
+++ b/Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-light
@@ -1,6 +1,3 @@
-[cursor]
-color=d4d4d4 555555
-
[colors]
foreground=696969
background=d4d4d4
@@ -22,3 +19,4 @@ bright6=333333
bright7=000000
selection-foreground=d4d4d4
selection-background=696969
+cursor=d4d4d4 555555
diff --git a/Assets/ColorScheme/Noctalia-default/terminal/foot/Noctalia-default-dark b/Assets/ColorScheme/Noctalia-default/terminal/foot/Noctalia-default-dark
index 7f528b8f..86da6444 100644
--- a/Assets/ColorScheme/Noctalia-default/terminal/foot/Noctalia-default-dark
+++ b/Assets/ColorScheme/Noctalia-default/terminal/foot/Noctalia-default-dark
@@ -1,6 +1,3 @@
-[cursor]
-color=070722 fff59b
-
[colors]
foreground=f3edf7
background=070722
@@ -22,3 +19,4 @@ bright6=9BFECE
bright7=ffffff
selection-foreground=070722
selection-background=f3edf7
+cursor=070722 fff59b
diff --git a/Assets/ColorScheme/Noctalia-default/terminal/foot/Noctalia-default-light b/Assets/ColorScheme/Noctalia-default/terminal/foot/Noctalia-default-light
index a8d78846..ee2e271d 100644
--- a/Assets/ColorScheme/Noctalia-default/terminal/foot/Noctalia-default-light
+++ b/Assets/ColorScheme/Noctalia-default/terminal/foot/Noctalia-default-light
@@ -1,6 +1,3 @@
-[cursor]
-color=e6e8fa 5d65f5
-
[colors]
foreground=4b55c8
background=e6e8fa
@@ -22,3 +19,4 @@ bright6=0e0e43
bright7=0e0e43
selection-foreground=e6e8fa
selection-background=4b55c8
+cursor=e6e8fa 5d65f5
diff --git a/Assets/ColorScheme/Noctalia-legacy/terminal/foot/Noctalia-legacy-dark b/Assets/ColorScheme/Noctalia-legacy/terminal/foot/Noctalia-legacy-dark
index 43c1498b..d23ad88e 100644
--- a/Assets/ColorScheme/Noctalia-legacy/terminal/foot/Noctalia-legacy-dark
+++ b/Assets/ColorScheme/Noctalia-legacy/terminal/foot/Noctalia-legacy-dark
@@ -1,6 +1,3 @@
-[cursor]
-color=1c1822 c7a1d8
-
[colors]
foreground=e9e4f0
background=1c1822
@@ -22,3 +19,4 @@ bright6=e0b7c9
bright7=ffffff
selection-foreground=1c1822
selection-background=e9e4f0
+cursor=1c1822 c7a1d8
diff --git a/Assets/ColorScheme/Noctalia-legacy/terminal/foot/Noctalia-legacy-light b/Assets/ColorScheme/Noctalia-legacy/terminal/foot/Noctalia-legacy-light
index 1790ae98..1b539c4d 100644
--- a/Assets/ColorScheme/Noctalia-legacy/terminal/foot/Noctalia-legacy-light
+++ b/Assets/ColorScheme/Noctalia-legacy/terminal/foot/Noctalia-legacy-light
@@ -1,6 +1,3 @@
-[cursor]
-color=f5f1fa 9b59ba
-
[colors]
foreground=1c1822
background=f5f1fa
@@ -22,3 +19,4 @@ bright6=c17093
bright7=1c1822
selection-foreground=f5f1fa
selection-background=1c1822
+cursor=f5f1fa 9b59ba
diff --git a/Assets/ColorScheme/Nord/terminal/foot/Nord-dark b/Assets/ColorScheme/Nord/terminal/foot/Nord-dark
index 8cf7c183..05862554 100644
--- a/Assets/ColorScheme/Nord/terminal/foot/Nord-dark
+++ b/Assets/ColorScheme/Nord/terminal/foot/Nord-dark
@@ -1,7 +1,4 @@
-[cursor]
-color=282828 eceff4
-
[colors]
foreground=d8dee9
background=2e3440
@@ -23,3 +20,4 @@ bright6=8fbcbb
bright7=eceff4
selection-foreground=4c566a
selection-background=eceff4
+cursor=282828 eceff4
diff --git a/Assets/ColorScheme/Nord/terminal/foot/Nord-light b/Assets/ColorScheme/Nord/terminal/foot/Nord-light
index 2eb5ad4f..b012c226 100644
--- a/Assets/ColorScheme/Nord/terminal/foot/Nord-light
+++ b/Assets/ColorScheme/Nord/terminal/foot/Nord-light
@@ -1,7 +1,4 @@
-[cursor]
-color=3b4252 7bb3c3
-
[colors]
foreground=414858
background=e5e9f0
@@ -23,3 +20,4 @@ bright6=82afae
bright7=eceff4
selection-foreground=4c556a
selection-background=d8dee9
+cursor=3b4252 7bb3c3
diff --git a/Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-dark b/Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-dark
index 06847ce9..7462c5a1 100644
--- a/Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-dark
+++ b/Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-dark
@@ -1,7 +1,4 @@
-[cursor]
-color=191724 e0def4
-
[colors]
foreground=e0def4
background=191724
@@ -23,3 +20,4 @@ bright6=ebbcba
bright7=e0def4
selection-foreground=e0def4
selection-background=403d52
+cursor=191724 e0def4
diff --git a/Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-light b/Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-light
index bf86fc77..4d74cf42 100644
--- a/Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-light
+++ b/Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-light
@@ -1,7 +1,4 @@
-[cursor]
-color=faf4ed 575279
-
[colors]
foreground=575279
background=faf4ed
@@ -23,3 +20,4 @@ bright6=d7827e
bright7=575279
selection-foreground=575279
selection-background=dfdad9
+cursor=faf4ed 575279
diff --git a/Assets/ColorScheme/Solarized/terminal/foot/Solarized-dark b/Assets/ColorScheme/Solarized/terminal/foot/Solarized-dark
index 63ad9f8e..46618196 100644
--- a/Assets/ColorScheme/Solarized/terminal/foot/Solarized-dark
+++ b/Assets/ColorScheme/Solarized/terminal/foot/Solarized-dark
@@ -1,6 +1,3 @@
-[cursor]
-color=073642 839496
-
[colors]
foreground=839496
background=002b36
@@ -21,4 +18,5 @@ bright5=6c71c4
bright6=93a1a1
bright7=fdf6e3
selection-foreground=93a1a1
-selection-background=073642
\ No newline at end of file
+selection-background=073642
+cursor=073642 839496
diff --git a/Assets/ColorScheme/Solarized/terminal/foot/Solarized-light b/Assets/ColorScheme/Solarized/terminal/foot/Solarized-light
index cef69200..b318ebe9 100644
--- a/Assets/ColorScheme/Solarized/terminal/foot/Solarized-light
+++ b/Assets/ColorScheme/Solarized/terminal/foot/Solarized-light
@@ -1,7 +1,4 @@
-[cursor]
-color=eee8d5 657b83
-
[colors]
foreground=657b83
background=fdf6e3
@@ -22,4 +19,5 @@ bright5=6c71c4
bright6=93a1a1
bright7=fdf6e3
selection-foreground=586e75
-selection-background=eee8d5
\ No newline at end of file
+selection-background=eee8d5
+cursor=eee8d5 657b83
diff --git a/Assets/ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-dark b/Assets/ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-dark
index f4d9dc41..794ff163 100644
--- a/Assets/ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-dark
+++ b/Assets/ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-dark
@@ -1,6 +1,3 @@
-[cursor]
-color=1a1b26 c0caf5
-
[colors]
foreground=c0caf5
background=1a1b26
@@ -21,4 +18,5 @@ bright5=bb9af7
bright6=7dcfff
bright7=c0caf5
selection-foreground=c0caf5
-selection-background=283457
\ No newline at end of file
+selection-background=283457
+cursor=1a1b26 c0caf5
diff --git a/Assets/ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-light b/Assets/ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-light
index 5ac8b4db..611a2721 100644
--- a/Assets/ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-light
+++ b/Assets/ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-light
@@ -1,6 +1,3 @@
-[cursor]
-color=e1e2e7 3760bf
-
[colors]
foreground=3760bf
background=e1e2e7
@@ -21,4 +18,5 @@ bright5=9854f1
bright6=007197
bright7=3760bf
selection-foreground=3760bf
-selection-background=99a7df
\ No newline at end of file
+selection-background=99a7df
+cursor=e1e2e7 3760bf
diff --git a/Assets/MatugenTemplates/kcolorscheme.colors b/Assets/MatugenTemplates/kcolorscheme.colors
new file mode 100644
index 00000000..195bcc48
--- /dev/null
+++ b/Assets/MatugenTemplates/kcolorscheme.colors
@@ -0,0 +1,146 @@
+[KDE]
+contrast=4
+
+[General]
+ColorScheme=Matugen
+Name=noctalia
+
+[ColorEffects:Disabled]
+Color={{colors.surface_dim.default.hex}}
+ColorAmount=0
+ColorEffect=0
+ContrastAmount=0.65
+ContrastEffect=1
+IntensityAmount=0.1
+IntensityEffect=2
+
+[ColorEffects:Inactive]
+ChangeSelectionColor=true
+Color={{colors.surface_variant.default.hex}}
+ColorAmount=0.025
+ColorEffect=2
+ContrastAmount=0.1
+ContrastEffect=2
+Enable=false
+IntensityAmount=0
+IntensityEffect=0
+
+[Colors:Button]
+BackgroundAlternate={{colors.surface_container_low.default.hex}}
+BackgroundNormal={{colors.surface_container_high.default.hex}}
+DecorationFocus={{colors.primary.default.hex}}
+DecorationHover={{colors.primary.default.hex}}
+ForegroundActive={{colors.primary.default.hex}}
+ForegroundInactive={{colors.on_surface_variant.default.hex}}
+ForegroundLink={{colors.secondary.default.hex}}
+ForegroundNegative={{colors.error.default.hex}}
+ForegroundNeutral={{colors.tertiary.default.hex}}
+ForegroundNormal={{colors.on_surface.default.hex}}
+ForegroundPositive={{colors.tertiary_fixed.default.hex}}
+ForegroundVisited={{colors.on_secondary_container.default.hex}}
+
+[Colors:Complementary]
+BackgroundAlternate={{colors.surface_container_low.default.hex}}
+BackgroundNormal={{colors.surface.default.hex}}
+DecorationFocus={{colors.primary.default.hex}}
+DecorationHover={{colors.primary.default.hex}}
+ForegroundActive={{colors.primary.default.hex}}
+ForegroundInactive={{colors.on_surface_variant.default.hex}}
+ForegroundLink={{colors.secondary.default.hex}}
+ForegroundNegative={{colors.error.default.hex}}
+ForegroundNeutral={{colors.tertiary.default.hex}}
+ForegroundNormal={{colors.on_primary_container.default.hex}}
+ForegroundPositive={{colors.tertiary_fixed.default.hex}}
+ForegroundVisited={{colors.on_secondary_container.default.hex}}
+
+[Colors:Header]
+BackgroundAlternate={{colors.surface.default.hex}}
+BackgroundNormal={{colors.surface_container.default.hex}}
+DecorationFocus={{colors.primary.default.hex}}
+DecorationHover={{colors.primary.default.hex}}
+ForegroundActive={{colors.primary.default.hex}}
+ForegroundInactive={{colors.on_surface_variant.default.hex}}
+ForegroundLink={{colors.secondary.default.hex}}
+ForegroundNegative={{colors.error.default.hex}}
+ForegroundNeutral={{colors.tertiary.default.hex}}
+ForegroundNormal={{colors.on_surface.default.hex}}
+ForegroundPositive={{colors.tertiary_fixed.default.hex}}
+ForegroundVisited={{colors.on_secondary_container.default.hex}}
+
+[Colors:Header][Inactive]
+BackgroundAlternate={{colors.surface_container.default.hex}}
+BackgroundNormal={{colors.surface.default.hex}}
+DecorationFocus={{colors.primary.default.hex}}
+DecorationHover={{colors.primary.default.hex}}
+ForegroundActive={{colors.primary.default.hex}}
+ForegroundInactive={{colors.on_surface_variant.default.hex}}
+ForegroundLink={{colors.secondary.default.hex}}
+ForegroundNegative={{colors.error.default.hex}}
+ForegroundNeutral={{colors.tertiary.default.hex}}
+ForegroundNormal={{colors.on_surface.default.hex}}
+ForegroundPositive={{colors.tertiary_fixed.default.hex}}
+ForegroundVisited={{colors.on_secondary_container.default.hex}}
+
+[Colors:Selection]
+BackgroundAlternate={{colors.surface_container_low.default.hex}}
+BackgroundNormal={{colors.primary.default.hex}}
+DecorationFocus={{colors.primary.default.hex}}
+DecorationHover={{colors.primary.default.hex}}
+ForegroundActive={{colors.on_primary.default.hex}}
+ForegroundInactive={{colors.on_surface_variant.default.hex}}
+ForegroundLink={{colors.secondary_fixed.default.hex}}
+ForegroundNegative={{colors.error_container.default.hex}}
+ForegroundNeutral={{colors.tertiary_fixed_dim.default.hex}}
+ForegroundNormal={{colors.on_primary.default.hex}}
+ForegroundPositive={{colors.tertiary_container.default.hex}}
+ForegroundVisited={{colors.on_secondary_container.default.hex}}
+
+[Colors:Tooltip]
+BackgroundAlternate={{colors.surface.default.hex}}
+BackgroundNormal={{colors.surface_container.default.hex}}
+DecorationFocus={{colors.primary.default.hex}}
+DecorationHover={{colors.primary.default.hex}}
+ForegroundActive={{colors.primary.default.hex}}
+ForegroundInactive={{colors.on_surface_variant.default.hex}}
+ForegroundLink={{colors.secondary.default.hex}}
+ForegroundNegative={{colors.error.default.hex}}
+ForegroundNeutral={{colors.tertiary.default.hex}}
+ForegroundNormal={{colors.on_background.default.hex}}
+ForegroundPositive={{colors.tertiary_fixed.default.hex}}
+ForegroundVisited={{colors.on_secondary_container.default.hex}}
+
+[Colors:View]
+BackgroundAlternate={{colors.surface_container.default.hex}}
+BackgroundNormal={{colors.background.default.hex}}
+DecorationFocus={{colors.on_primary_container.default.hex}}
+DecorationHover={{colors.on_primary.default.hex}}
+ForegroundActive={{colors.primary.default.hex}}
+ForegroundInactive={{colors.on_surface_variant.default.hex}}
+ForegroundLink={{colors.secondary.default.hex}}
+ForegroundNegative={{colors.error.default.hex}}
+ForegroundNeutral={{colors.tertiary.default.hex}}
+ForegroundNormal={{colors.on_background.default.hex}}
+ForegroundPositive={{colors.tertiary_fixed.default.hex}}
+ForegroundVisited={{colors.on_secondary_container.default.hex}}
+
+[Colors:Window]
+BackgroundAlternate={{colors.primary_container.default.hex}}
+BackgroundNormal={{colors.surface_container.default.hex}}
+DecorationFocus={{colors.primary.default.hex}}
+DecorationHover={{colors.primary.default.hex}}
+ForegroundActive={{colors.primary.default.hex}}
+ForegroundInactive={{colors.on_surface_variant.default.hex}}
+ForegroundLink={{colors.secondary.default.hex}}
+ForegroundNegative={{colors.error.default.hex}}
+ForegroundNeutral={{colors.tertiary.default.hex}}
+ForegroundNormal={{colors.on_background.default.hex}}
+ForegroundPositive={{colors.tertiary_fixed.default.hex}}
+ForegroundVisited={{colors.on_secondary_container.default.hex}}
+
+[WM]
+activeBackground={{colors.primary_container.default.hex}}
+activeBlend={{colors.on_primary_container.default.hex}}
+activeForeground={{colors.on_primary_container.default.hex}}
+inactiveBackground={{colors.surface.default.hex}}
+inactiveBlend={{colors.on_surface_variant.default.hex}}
+inactiveForeground={{colors.on_surface_variant.default.hex}}
diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json
index a45e6199..82bff890 100644
--- a/Assets/Translations/de.json
+++ b/Assets/Translations/de.json
@@ -13,32 +13,6 @@
},
"select-avatar": "Avatar-Bild auswählen"
},
- "ui": {
- "section": {
- "label": "Benutzeroberfläche",
- "description": "Passen Sie das Aussehen, die Haptik und das Verhalten der Oberfläche an."
- },
- "tooltips": {
- "label": "Tooltips anzeigen",
- "description": "Tooltips in der gesamten Benutzeroberfläche aktivieren oder deaktivieren."
- },
- "dim-desktop": {
- "label": "Desktop abdunkeln",
- "description": "Desktop abdunkeln, wenn Panels oder Menüs geöffnet sind."
- },
- "border-radius": {
- "label": "Eckenradius",
- "description": "Steuert die Rundung der Ecken von Fenstern, Buttons und anderen Elementen."
- },
- "animation-speed": {
- "label": "Animationsgeschwindigkeit",
- "description": "Globale Animationsgeschwindigkeit anpassen."
- },
- "animation-disable": {
- "label": "UI-Animationen deaktivieren",
- "description": "Alle Animationen für eine schnellere, reaktionsschnellere Erfahrung deaktivieren."
- }
- },
"screen-corners": {
"section": {
"label": "Bildschirmecken",
@@ -54,7 +28,8 @@
},
"radius": {
"label": "Bildschirmecken-Radius",
- "description": "Rundung der Bildschirmecken anpassen."
+ "description": "Rundung der Bildschirmecken anpassen.",
+ "reset": "Eckenradius des Bildschirms zurücksetzen"
}
},
"fonts": {
@@ -83,16 +58,6 @@
}
},
"reset-scaling": "Skalierung zurücksetzen"
- },
- "control-center": {
- "section": {
- "label": "Kontrollzentrum",
- "description": "Konfigurieren Sie die Positionierung und das Verhalten des Kontrollzentrum-Panels."
- },
- "position": {
- "label": "Position",
- "description": "Wählen Sie, wo das Kontrollzentrum-Panel beim Öffnen erscheint."
- }
}
},
"audio": {
@@ -179,11 +144,9 @@
"monitors": {
"section": {
"label": "Monitor-spezifische Einstellungen",
- "description": "Skalierung und Helligkeit für jeden Bildschirm anpassen."
+ "description": "Helligkeitseinstellungen für jedes Display anpassen."
},
- "scale": "Skalierung",
"brightness": "Helligkeit",
- "reset-scaling": "Skalierung zurücksetzen",
"brightness-step": {
"label": "Helligkeits-Schrittgröße",
"description": "Schrittgröße für Helligkeitsänderungen anpassen (Mausrad und Tastenkombinationen)."
@@ -259,17 +222,20 @@
"widgets": {
"section": {
"label": "Widget-Positionierung",
- "description": "Widgets per Drag & Drop innerhalb jeder Sektion neu anordnen oder Add/Remove-Buttons zum Verwalten verwenden."
+ "description": "Widgets per Drag & Drop neu anordnen. Abzeichen zeigen die Verwendung an: [L]inks, [M]itte, [R]echts."
}
},
"monitors": {
"section": {
"label": "Monitor-Anzeige",
"description": "Statusleiste auf bestimmten Monitoren anzeigen. Standard ist alle, wenn keine ausgewählt sind."
- },
- "only-same-output": {
- "label": "Nur Apps vom gleichen Bildschirm",
- "description": "Zeige nur Apps vom dem Bildschirm an, wo sich das Dock befindet."
+ }
+ },
+ "tray": {
+ "blacklist": {
+ "label": "Ausschlussliste",
+ "description": "Füge Ausschlussregeln für die Tray-Symbolleiste hinzu, unterstützt Platzhalter (*).",
+ "placeholder": "z.B., nm-applet, Fcitx*"
}
}
},
@@ -294,6 +260,10 @@
"floating-distance": {
"label": "Dock-Schwebeabstand",
"description": "Schwebeabstand vom Bildschirmrand anpassen."
+ },
+ "colorize-icons": {
+ "label": "Symbole einfärben",
+ "description": "Theme-Farben auf Dock-App-Symbole anwenden (nur nicht fokussierte Apps)."
}
},
"monitors": {
@@ -406,6 +376,10 @@
"label": "Bildschirmanzeige aktivieren",
"description": "Lautstärke- und Helligkeitsänderungen in Echtzeit anzeigen."
},
+ "always-on-top": {
+ "label": "Immer im Vordergrund",
+ "description": "Bildschirmanzeige über Vollbildfenstern und anderen Ebenen anzeigen."
+ },
"location": {
"label": "Position",
"description": "Wo Bildschirmanzeigen erscheinen."
@@ -533,6 +507,9 @@
},
"qt": {
"description": "Schreibt {filepath}"
+ },
+ "kcolorscheme": {
+ "description": "Schreibt {filepath}"
}
},
"terminal": {
@@ -558,9 +535,9 @@
"description": "Schreibt {filepath} und lädt neu",
"description-missing": "Erfordert fuzzel Starter"
},
- "vesktop": {
- "description": "Schreibt {filepath}",
- "description-missing": "Erfordert vesktop Discord-Client"
+ "discord": {
+ "description": "Schreibt {filepath} für {client}",
+ "description-missing": "Kein Discord-Client erkannt. Installieren Sie vesktop, webcord, armcord, equibop, lightcord oder dorion."
},
"pywalfox": {
"description": "Schreibt {filepath} und führt pywalfox update aus",
@@ -707,7 +684,8 @@
"description": "Ein Dankeschön an unseren {count} großartigen Mitwirkenden!",
"description_plural": "Ein Dankeschön an unsere {count} großartigen Mitwirkenden!"
}
- }
+ },
+ "support": "Unterstützen Sie uns"
},
"hooks": {
"title": "Hooks",
@@ -741,6 +719,69 @@
"description": "• Hintergrundbild-Hook: $1 = Hintergrundbild-Pfad, $2 = Bildschirmname\n• Theme-Wechsel-Hook: $1 = true/false (Dunkelmodus-Status)"
}
}
+ },
+ "control-center": {
+ "position": {
+ "description": "Wählen Sie aus, wo das Kontrollzentrum angezeigt wird, wenn es geöffnet wird.",
+ "label": "Position"
+ },
+ "section": {
+ "description": "Konfigurieren Sie die Positionierung und das Verhalten des Control Center-Panels.",
+ "label": "Aussehen"
+ },
+ "title": "Kontrollzentrum",
+ "cards": {
+ "section": {
+ "description": "Passen Sie an, welche Steuerelemente im Kontrollzentrum angezeigt werden und in welcher Reihenfolge.",
+ "label": "Karten"
+ }
+ },
+ "shortcuts": {
+ "section": {
+ "description": "Konfigurieren und verwalten Sie die Verknüpfungs-Widgets.",
+ "label": "Widgets für Kurzbefehle"
+ },
+ "sectionLeft": "Links",
+ "sectionRight": "Richtig"
+ }
+ },
+ "user-interface": {
+ "animation-disable": {
+ "description": "Deaktivieren Sie alle Animationen für eine schnellere und reaktionsfreudigere Erfahrung.",
+ "label": "UI-Animationen deaktivieren"
+ },
+ "animation-speed": {
+ "description": "Globale Animationsgeschwindigkeit anpassen.",
+ "label": "Animationsgeschwindigkeit",
+ "reset": "Animationsgeschwindigkeit zurücksetzen"
+ },
+ "border-radius": {
+ "description": "Steuert die Eckenrundung von Fenstern, Schaltflächen und anderen Elementen.",
+ "label": "Eckenradius",
+ "reset": "Rahmenradius zurücksetzen"
+ },
+ "compact-lockscreen": {
+ "description": "Zeige nur die Login-Eingabe und Systemsteuerung, blende Wetter- und Medien-Widgets aus.",
+ "label": "Kompakter Sperrbildschirm"
+ },
+ "dim-desktop": {
+ "description": "Den Desktop abdunkeln, wenn Bedienfelder oder Menüs geöffnet sind.",
+ "label": "Dim Desktop"
+ },
+ "scaling": {
+ "description": "Ändert die Größe der allgemeinen Benutzeroberfläche, mit Ausnahme der Leiste.",
+ "label": "Oberflächenskalierung",
+ "reset-scaling": "Schnittstellenskalierung zurücksetzen"
+ },
+ "section": {
+ "description": "Passen Sie das Aussehen, die Haptik und das Verhalten der Benutzeroberfläche an.",
+ "label": "Aussehen"
+ },
+ "title": "Benutzeroberfläche",
+ "tooltips": {
+ "description": "Tooltips in der gesamten Benutzeroberfläche aktivieren oder deaktivieren.",
+ "label": "Tooltips anzeigen"
+ }
}
},
"general": {
@@ -766,7 +807,11 @@
},
"file-picker": {
"select-folder": "Ordner auswählen",
- "select-file": "Datei auswählen"
+ "select-file": "Datei auswählen",
+ "cancel": "Abbrechen",
+ "search-placeholder": "Dateien und Ordner suchen...",
+ "select-current": "Aktuelle auswählen",
+ "title": "Dateiauswahl"
},
"datetime-tokens": {
"common": {
@@ -857,9 +902,9 @@
"search-placeholder": "Widgets suchen..."
},
"active-window": {
- "auto-hide": {
- "label": "Automatisch ausblenden",
- "description": "Widget automatisch ausblenden, wenn kein Fenster aktiv ist."
+ "hide-mode": {
+ "label": "Ausblendmodus",
+ "description": "Steuert das Verhalten des Widgets, wenn kein Fenster aktiv ist."
},
"show-app-icon": {
"label": "App-Symbol anzeigen",
@@ -872,6 +917,10 @@
"width": {
"description": "Steuert die horizontale Größe des Widgets.",
"label": "Widget-Breite"
+ },
+ "colorize-icons": {
+ "label": "Symbole einfärben",
+ "description": "Theme-Farben auf das aktive Fenster-Symbol anwenden."
}
},
"system-monitor": {
@@ -1002,9 +1051,9 @@
}
},
"media-mini": {
- "auto-hide": {
- "label": "Automatisch ausblenden",
- "description": "Widget automatisch ausblenden, wenn keine Medien abgespielt werden."
+ "hide-mode": {
+ "label": "Ausblendmodus",
+ "description": "Steuert das Verhalten des Widgets, wenn keine Medien abgespielt werden."
},
"show-album-art": {
"label": "Albumcover anzeigen",
@@ -1065,6 +1114,16 @@
"only-same-output": {
"label": "Nur vom gleichen Bildschirm",
"description": "Zeige nur Apps vom dem Bildschirm an, wo sich die Taskbar befindet."
+ },
+ "colorize-icons": {
+ "label": "Symbole einfärben",
+ "description": "Theme-Farben auf Taskbar-Symbole anwenden."
+ }
+ },
+ "tray": {
+ "colorize-icons": {
+ "label": "Symbole einfärben",
+ "description": "Theme-Farben auf Tray-Symbole anwenden."
}
}
}
@@ -1234,7 +1293,8 @@
"density": {
"compact": "Kompakt",
"default": "Standard",
- "comfortable": "Bequem"
+ "comfortable": "Bequem",
+ "mini": "Mini"
}
},
"launcher": {
@@ -1259,6 +1319,11 @@
"bottom_right": "Unten rechts",
"bottom_center": "Unten mittig",
"top_center": "Oben mittig"
+ },
+ "quickSettingsStyle": {
+ "modern": "Modern",
+ "classic": "Klassisch",
+ "compact": "Kompakt"
}
},
"osd": {
@@ -1318,6 +1383,11 @@
"microphone-input": "Mikrofon-Eingabe",
"both": "System-Ausgabe + Mikrofon-Eingabe"
}
+ },
+ "hide-modes": {
+ "hidden": "Ausblenden, wenn leer",
+ "transparent": "Transparent, wenn leer",
+ "visible": "Immer sichtbar"
}
},
"session-menu": {
@@ -1355,9 +1425,9 @@
"calculator-error": "Fehler"
},
"system": {
- "uptime": "System-Laufzeit: {uptime}",
+ "uptime": "Laufzeit: {uptime}",
"welcome-back": "Willkommen zurück,",
- "monitor-description": "{model} ({width}x{height})",
+ "monitor-description": "{model} ({width}x{height} @ {scale}x)",
"scaling-percentage": "{percentage}%",
"location-display": "{name} ({coordinates})",
"signal-strength": "{signal}%",
@@ -1398,6 +1468,78 @@
"restart": "Neu starten",
"suspend": "Ruhezustand"
},
+ "quickSettings": {
+ "notifications": {
+ "label": {
+ "enabled": "Benachrichtigungen",
+ "disabled": "Nicht stören"
+ },
+ "tooltip": {
+ "action": "Linksklick: Benachrichtigungsverlauf öffnen\nRechtsklick: Nicht stören umschalten"
+ }
+ },
+ "screenRecorder": {
+ "label": {
+ "recording": "Stopp",
+ "stopped": "Aufnehmen"
+ },
+ "tooltip": {
+ "action": "Klicken zum Starten/Stoppen der Bildschirmaufnahme"
+ }
+ },
+ "powerProfile": {
+ "label": {
+ "unavailable": "Energieprofil"
+ },
+ "tooltip": {
+ "action": "Klicken zum Wechseln des Energieprofils"
+ }
+ },
+ "wifi": {
+ "label": {
+ "ethernet": "Ethernet",
+ "wifi": "Wi-Fi",
+ "disconnected": "Wi-Fi getrennt"
+ },
+ "tooltip": {
+ "action": "Klicken zum Verwalten der Wi-Fi-Verbindungen"
+ }
+ },
+ "bluetooth": {
+ "label": {
+ "enabled": "Bluetooth",
+ "disabled": "Bluetooth"
+ },
+ "tooltip": {
+ "action": "Klicken zum Verwalten der Bluetooth-Geräte"
+ }
+ },
+ "nightLight": {
+ "label": {
+ "enabled": "Nachtlicht",
+ "forced": "Nachtlicht",
+ "disabled": "Nachtlicht"
+ },
+ "tooltip": {
+ "action": "Klicken zum Wechseln des Nachtlicht-Modus\nRechtsklick: Einstellungen öffnen"
+ }
+ },
+ "wallpaperSelector": {
+ "label": "Hintergrundbild",
+ "tooltip": {
+ "action": "Linksklick: Hintergrundbildauswahl öffnen\nRechtsklick: Zufälliges Hintergrundbild setzen"
+ }
+ },
+ "keepAwake": {
+ "label": {
+ "enabled": "Wach halten",
+ "disabled": "Wach halten"
+ },
+ "tooltip": {
+ "action": "Klicken zum Umschalten des Wach-halten-Modus"
+ }
+ }
+ },
"toast": {
"night-light": {
"enabled": "Aktiviert",
@@ -1445,6 +1587,9 @@
"enabled": "Aktiviert",
"disabled": "Deaktiviert"
},
+ "kofi": {
+ "opened": "Ko-fi-Seite in Ihrem Browser geöffnet"
+ },
"do-not-disturb": {
"enabled": "'Nicht stören' aktiviert",
"disabled": "'Nicht stören' deaktiviert",
diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json
index 36d37ebe..7bf2adb1 100644
--- a/Assets/Translations/en.json
+++ b/Assets/Translations/en.json
@@ -13,32 +13,6 @@
},
"select-avatar": "Select avatar image"
},
- "ui": {
- "section": {
- "label": "User interface",
- "description": "Customize the look, feel, and behavior of the interface."
- },
- "tooltips": {
- "label": "Show tooltips",
- "description": "Enable or disable tooltips throughout the interface."
- },
- "dim-desktop": {
- "label": "Dim desktop",
- "description": "Dim the desktop when panels or menus are open."
- },
- "border-radius": {
- "label": "Border radius",
- "description": "Controls the corner roundness of windows, buttons, and other elements."
- },
- "animation-speed": {
- "label": "Animation speed",
- "description": "Adjust global animation speed."
- },
- "animation-disable": {
- "label": "Disable UI Animations",
- "description": "Disable all animations for a faster, more responsive experience."
- }
- },
"screen-corners": {
"section": {
"label": "Screen corners",
@@ -54,7 +28,8 @@
},
"radius": {
"label": "Screen corners radius",
- "description": "Adjust the rounded corners of the screen."
+ "description": "Adjust the rounded corners of the screen.",
+ "reset": "Reset screen corners radius"
}
},
"fonts": {
@@ -83,17 +58,8 @@
"description": "Increase or decrease the size of the monospaced text."
}
}
- },
- "control-center": {
- "section": {
- "label": "Control Center",
- "description": "Configure the Control Center panel positioning and behavior."
- },
- "position": {
- "label": "Position",
- "description": "Choose where the Control Center panel appears when opened."
- }
}
+
},
"audio": {
"title": "Audio",
@@ -179,11 +145,9 @@
"monitors": {
"section": {
"label": "Per-monitor settings",
- "description": "Adjust scaling and brightness for each display."
+ "description": "Adjust settings brightness for each display."
},
- "scale": "Scale",
"brightness": "Brightness",
- "reset-scaling": "Reset scaling",
"brightness-step": {
"label": "Brightness step size",
"description": "Adjust the step size for brightness changes (scroll wheel and keyboard shortcuts)."
@@ -259,7 +223,7 @@
"widgets": {
"section": {
"label": "Widgets positioning",
- "description": "Drag and drop widgets to reorder them within each section, or use the add/remove buttons to manage widgets."
+ "description": "Drag and drop widgets to reorder them. Badges indicate usage: [L]eft, [C]enter, [R]ight."
}
},
"monitors": {
@@ -267,6 +231,13 @@
"label": "Monitors display",
"description": "Show bar on specific monitors. Defaults to all if none are chosen."
}
+ },
+ "tray": {
+ "blacklist": {
+ "label": "Blacklist",
+ "description": "Add tray exclusion rules, supports wildcards (*).",
+ "placeholder": "e.g., nm-applet, Fcitx*"
+ }
}
},
"dock": {
@@ -290,6 +261,10 @@
"floating-distance": {
"label": "Dock floating distance",
"description": "Adjust the floating distance from the screen edge."
+ },
+ "colorize-icons": {
+ "label": "Colorize Icons",
+ "description": "Apply theme colors to dock app icons (non-focused apps only)."
}
},
"monitors": {
@@ -402,6 +377,10 @@
"label": "Enable on screen display",
"description": "Show volume and brightness changes in real-time."
},
+ "always-on-top": {
+ "label": "Always on top",
+ "description": "Display OSD above fullscreen windows and other layers."
+ },
"location": {
"label": "Location",
"description": "Where on-screen displays appear."
@@ -533,6 +512,9 @@
},
"qt": {
"description": "Write {filepath}"
+ },
+ "kcolorscheme": {
+ "description": "Write {filepath}"
}
},
"terminal": {
@@ -558,9 +540,9 @@
"description": "Write {filepath} and reload",
"description-missing": "Requires {app} to be installed"
},
- "vesktop": {
- "description": "Write {filepath}",
- "description-missing": "Requires {app} to be installed"
+ "discord": {
+ "description": "Write {filepath} for {client}",
+ "description-missing": "No Discord client detected. Install vesktop, webcord, armcord, equibop, lightcord, or dorion."
},
"pywalfox": {
"description": "Write {filepath} and run pywalfox update",
@@ -703,7 +685,8 @@
"description": "Shout-out to our {count} awesome contributor!",
"description_plural": "Shout-out to our {count} awesome contributors!"
}
- }
+ },
+ "support": "Support us"
},
"hooks": {
"title": "Hooks",
@@ -737,6 +720,69 @@
"description": "• Wallpaper Hook: $1 = wallpaper path, $2 = screen name\n• Theme Toggle Hook: $1 = true/false (dark mode state)"
}
}
+ },
+ "control-center": {
+ "title": "Control Center",
+ "section": {
+ "label": "Appearance",
+ "description": "Configure the Control Center panel positioning and behavior."
+ },
+ "position": {
+ "label": "Position",
+ "description": "Choose where the Control Center panel appears when opened."
+ },
+ "cards": {
+ "section": {
+ "label": "Cards",
+ "description": "Customize which controls appear in the Control Center and in what order."
+ }
+ },
+ "shortcuts": {
+ "section": {
+ "label": "Shortcuts widgets",
+ "description": "Configure and manage the shortcuts widgets."
+ },
+ "sectionLeft": "Left",
+ "sectionRight": "Right"
+ }
+ },
+ "user-interface": {
+ "title": "User interface",
+ "section": {
+ "label": "Appearance",
+ "description": "Customize the look, feel, and behavior of the interface."
+ },
+ "tooltips": {
+ "label": "Show tooltips",
+ "description": "Enable or disable tooltips throughout the interface."
+ },
+ "scaling": {
+ "label": "Interface scaling",
+ "description": "Changes the size of the general user interface, excluding the bar.",
+ "reset-scaling": "Reset interface scaling"
+ },
+ "dim-desktop": {
+ "label": "Dim desktop",
+ "description": "Dim the desktop when panels or menus are open."
+ },
+ "compact-lockscreen": {
+ "label": "Compact lock screen",
+ "description": "Show only the login input and system controls, hiding weather and media widgets."
+ },
+ "border-radius": {
+ "label": "Border radius",
+ "description": "Controls the corner roundness of windows, buttons, and other elements.",
+ "reset": "Reset border radius"
+ },
+ "animation-speed": {
+ "label": "Animation speed",
+ "description": "Adjust global animation speed.",
+ "reset": "Reset animation speed"
+ },
+ "animation-disable": {
+ "label": "Disable UI Animations",
+ "description": "Disable all animations for a faster, more responsive experience."
+ }
}
},
"widgets": {
@@ -744,8 +790,12 @@
"placeholder": "Placeholder"
},
"file-picker": {
+ "title": "File Picker",
"select-folder": "Select Folder",
- "select-file": "Select File"
+ "select-file": "Select File",
+ "search-placeholder": "Search files and folders...",
+ "select-current": "Select Current",
+ "cancel": "Cancel"
},
"datetime-tokens": {
"common": {
@@ -836,9 +886,9 @@
"search-placeholder": "Search widget..."
},
"active-window": {
- "auto-hide": {
- "label": "Hide automatically",
- "description": "Automatically hide the widget when no window is active."
+ "hide-mode": {
+ "label": "Hiding mode",
+ "description": "Controls how the widget behaves when no window is active."
},
"show-app-icon": {
"label": "Show app icon",
@@ -851,6 +901,10 @@
"width": {
"label": "Widget Width",
"description": "Controls the horizontal size of the widget."
+ },
+ "colorize-icons": {
+ "label": "Colorize Icons",
+ "description": "Apply theme colors to active window icon."
}
},
"system-monitor": {
@@ -981,9 +1035,9 @@
}
},
"media-mini": {
- "auto-hide": {
- "label": "Hide automatically",
- "description": "Automatically hide the widget when no media is playing."
+ "hide-mode": {
+ "label": "Hiding mode",
+ "description": "Controls how the widget behaves when no media is playing."
},
"show-album-art": {
"label": "Show album art",
@@ -1044,6 +1098,16 @@
"only-same-output": {
"label": "Only from same output",
"description": "Show only apps from the output where the bar is located."
+ },
+ "colorize-icons": {
+ "label": "Colorize Icons",
+ "description": "Apply theme colors to taskbar icons."
+ }
+ },
+ "tray": {
+ "colorize-icons": {
+ "label": "Colorize Icons",
+ "description": "Apply theme colors to tray icons."
}
}
}
@@ -1211,6 +1275,7 @@
"right": "Right"
},
"density": {
+ "mini": "Mini",
"compact": "Compact",
"default": "Default",
"comfortable": "Comfortable"
@@ -1238,6 +1303,11 @@
"bottom_right": "Bottom right",
"bottom_center": "Bottom center",
"top_center": "Top center"
+ },
+ "quickSettingsStyle": {
+ "modern": "Modern",
+ "classic": "Classic",
+ "compact": "Compact"
}
},
"osd": {
@@ -1274,6 +1344,11 @@
"hover": "Scroll On Hover",
"never": "Never Scroll"
},
+ "hide-modes": {
+ "visible": "Always Visible",
+ "hidden": "Hide When Empty",
+ "transparent": "Transparent When Empty"
+ },
"frame-rates": {
"fps": "{fps} FPS"
},
@@ -1334,9 +1409,9 @@
"calculator-error": "Error"
},
"system": {
- "uptime": "System uptime: {uptime}",
+ "uptime": "Uptime: {uptime}",
"welcome-back": "Welcome back,",
- "monitor-description": "{model} ({width}x{height})",
+ "monitor-description": "{model} ({width}x{height} @ {scale}x)",
"scaling-percentage": "{percentage}%",
"location-display": "{name} ({coordinates})",
"signal-strength": "{signal}%",
@@ -1359,6 +1434,78 @@
"restart": "Restart",
"suspend": "Suspend"
},
+ "quickSettings": {
+ "notifications": {
+ "label": {
+ "enabled": "Notifications",
+ "disabled": "Do Not Disturb"
+ },
+ "tooltip": {
+ "action": "Left click: Open notification history\nRight click: Toggle Do Not Disturb"
+ }
+ },
+ "screenRecorder": {
+ "label": {
+ "recording": "Stop",
+ "stopped": "Record"
+ },
+ "tooltip": {
+ "action": "Click to start/stop screen recording"
+ }
+ },
+ "powerProfile": {
+ "label": {
+ "unavailable": "Power Profile"
+ },
+ "tooltip": {
+ "action": "Click to cycle power profile"
+ }
+ },
+ "wifi": {
+ "label": {
+ "ethernet": "Ethernet",
+ "wifi": "Wi-Fi",
+ "disconnected": "Wi-Fi Disconnected"
+ },
+ "tooltip": {
+ "action": "Click to manage Wi-Fi connections"
+ }
+ },
+ "bluetooth": {
+ "label": {
+ "enabled": "Bluetooth",
+ "disabled": "Bluetooth"
+ },
+ "tooltip": {
+ "action": "Click to manage Bluetooth devices"
+ }
+ },
+ "nightLight": {
+ "label": {
+ "enabled": "Night Light",
+ "forced": "Night Light",
+ "disabled": "Night Light"
+ },
+ "tooltip": {
+ "action": "Click to cycle Night Light mode\nRight click: Open settings"
+ }
+ },
+ "wallpaperSelector": {
+ "label": "Wallpaper",
+ "tooltip": {
+ "action": "Left click: Open wallpaper selector\nRight click: Set random wallpaper"
+ }
+ },
+ "keepAwake": {
+ "label": {
+ "enabled": "Keep Awake",
+ "disabled": "Keep Awake"
+ },
+ "tooltip": {
+ "action": "Click to toggle keep awake mode"
+ }
+ }
+ },
"toast": {
"night-light": {
"enabled": "Enabled",
@@ -1406,6 +1553,9 @@
"enabled": "Enabled",
"disabled": "Disabled"
},
+ "kofi": {
+ "opened": "Ko-fi page opened in your browser"
+ },
"do-not-disturb": {
"enabled": "'Do not disturb' enabled",
"disabled": "'Do not disturb' disabled",
diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json
index aa648d97..d5e30bc1 100644
--- a/Assets/Translations/es.json
+++ b/Assets/Translations/es.json
@@ -13,32 +13,6 @@
},
"select-avatar": "Seleccionar imagen de avatar"
},
- "ui": {
- "section": {
- "label": "Interfaz de usuario",
- "description": "Personaliza la apariencia, sensación y comportamiento de la interfaz."
- },
- "tooltips": {
- "label": "Mostrar tooltips",
- "description": "Activar o desactivar tooltips en toda la interfaz."
- },
- "dim-desktop": {
- "label": "Atenuar escritorio",
- "description": "Atenúa el escritorio cuando los paneles o menús están abiertos."
- },
- "border-radius": {
- "label": "Radio del borde",
- "description": "Controla la redondez de las esquinas de ventanas, botones y otros elementos."
- },
- "animation-speed": {
- "label": "Velocidad de la animación",
- "description": "Ajusta la velocidad de la animación global."
- },
- "animation-disable": {
- "label": "Desactivar animaciones de UI",
- "description": "Desactivar todas las animaciones para una experiencia más rápida y responsiva."
- }
- },
"screen-corners": {
"section": {
"label": "Esquinas de la pantalla",
@@ -54,7 +28,8 @@
},
"radius": {
"label": "Radio de las esquinas de la pantalla",
- "description": "Ajusta las esquinas redondeadas de la pantalla."
+ "description": "Ajusta las esquinas redondeadas de la pantalla.",
+ "reset": "Restablecer el radio de las esquinas de la pantalla"
}
},
"fonts": {
@@ -83,16 +58,6 @@
}
},
"reset-scaling": "Restablecer la escala"
- },
- "control-center": {
- "section": {
- "label": "Centro de control",
- "description": "Configurar el posicionamiento y comportamiento del panel del centro de control."
- },
- "position": {
- "label": "Posición",
- "description": "Elige dónde aparece el panel del centro de control cuando se abre."
- }
}
},
"audio": {
@@ -179,11 +144,9 @@
"monitors": {
"section": {
"label": "Configuración por monitor",
- "description": "Ajusta la escala y el brillo para cada pantalla."
+ "description": "Ajustar el brillo de la configuración para cada pantalla."
},
- "scale": "Escala",
"brightness": "Brillo",
- "reset-scaling": "Restablecer escala",
"brightness-step": {
"label": "Tamaño del paso de brillo",
"description": "Ajusta el tamaño del paso para los cambios de brillo (rueda de desplazamiento y atajos de teclado)."
@@ -259,7 +222,7 @@
"widgets": {
"section": {
"label": "Posicionamiento de widgets",
- "description": "Arrastra y suelta widgets para reordenarlos dentro de cada sección, o usa los botones de añadir/eliminar para gestionar los widgets."
+ "description": "Arrastra y suelta widgets para reordenarlos. Las insignias indican el uso: [I]zquierda, [C]entro, [D]erecha."
}
},
"monitors": {
@@ -267,6 +230,13 @@
"label": "Visualización en monitores",
"description": "Muestra la barra en monitores específicos. Por defecto, se muestra en todos si no se elige ninguno."
}
+ },
+ "tray": {
+ "blacklist": {
+ "label": "Lista negra",
+ "description": "Agregar reglas de exclusión de la bandeja, admite comodines (*).",
+ "placeholder": "ej., nm-applet, Fcitx*"
+ }
}
},
"dock": {
@@ -290,6 +260,10 @@
"floating-distance": {
"label": "Distancia de flotación del dock",
"description": "Ajusta la distancia de flotación desde el borde de la pantalla."
+ },
+ "colorize-icons": {
+ "label": "Colorear iconos",
+ "description": "Aplicar colores del tema a los iconos de aplicaciones del dock (solo aplicaciones no enfocadas)."
}
},
"monitors": {
@@ -402,6 +376,10 @@
"label": "Activar visualización en pantalla",
"description": "Mostrar cambios de volumen y brillo en tiempo real."
},
+ "always-on-top": {
+ "label": "Siempre encima",
+ "description": "Mostrar OSD por encima de ventanas de pantalla completa y otras capas."
+ },
"location": {
"label": "Ubicación",
"description": "Dónde aparece la visualización en pantalla."
@@ -529,6 +507,9 @@
},
"qt": {
"description": "Escribir {filepath}"
+ },
+ "kcolorscheme": {
+ "description": "Escribir {filepath}"
}
},
"terminal": {
@@ -554,9 +535,9 @@
"description": "Escribir {filepath} y recargar",
"description-missing": "Requiere que {app} esté instalado"
},
- "vesktop": {
- "description": "Escribir {filepath}",
- "description-missing": "Requiere que {app} esté instalado"
+ "discord": {
+ "description": "Escribir {filepath} para {client}",
+ "description-missing": "No se detectó cliente de Discord. Instala vesktop, webcord, armcord, equibop, lightcord o dorion."
},
"pywalfox": {
"description": "Escribir {filepath} y ejecutar pywalfox update",
@@ -703,7 +684,8 @@
"description": "¡Un saludo a nuestro increíble colaborador número {count}!",
"description_plural": "¡Un saludo a nuestros {count} increíbles colaboradores!"
}
- }
+ },
+ "support": "Apóyanos"
},
"hooks": {
"title": "Hooks",
@@ -737,6 +719,69 @@
"description": "• Hook de fondo de pantalla: $1 = ruta del fondo de pantalla, $2 = nombre de la pantalla\n• Hook de cambio de tema: $1 = true/false (estado del modo oscuro)"
}
}
+ },
+ "control-center": {
+ "position": {
+ "description": "Elige dónde aparece el panel del Centro de control cuando se abre.",
+ "label": "Posición"
+ },
+ "section": {
+ "description": "Configurar el posicionamiento y el comportamiento del panel del Centro de control.",
+ "label": "Apariencia"
+ },
+ "title": "Centro de control",
+ "cards": {
+ "section": {
+ "description": "Personaliza qué controles aparecen en el Centro de control y en qué orden.",
+ "label": "Tarjetas"
+ }
+ },
+ "shortcuts": {
+ "section": {
+ "description": "Configurar y administrar los widgets de accesos directos.",
+ "label": "Widgets de accesos directos"
+ },
+ "sectionLeft": "Izquierda",
+ "sectionRight": "Derecha"
+ }
+ },
+ "user-interface": {
+ "animation-disable": {
+ "description": "Desactiva todas las animaciones para una experiencia más rápida y con mayor capacidad de respuesta.",
+ "label": "Desactivar animaciones de la interfaz de usuario"
+ },
+ "animation-speed": {
+ "description": "Ajustar la velocidad global de la animación.",
+ "label": "Velocidad de animación",
+ "reset": "Restablecer la velocidad de la animación"
+ },
+ "border-radius": {
+ "description": "Controla la redondez de las esquinas de las ventanas, los botones y otros elementos.",
+ "label": "Radio de borde",
+ "reset": "Restablecer el radio del borde"
+ },
+ "compact-lockscreen": {
+ "description": "Mostrar solo el campo de inicio de sesión y los controles del sistema, ocultando los widgets del clima y multimedia.",
+ "label": "Pantalla de bloqueo compacta"
+ },
+ "dim-desktop": {
+ "description": "Atenuar el escritorio cuando los paneles o menús estén abiertos.",
+ "label": "Dim escritorio"
+ },
+ "scaling": {
+ "description": "Cambia el tamaño de la interfaz de usuario general, excluyendo la barra.",
+ "label": "Escalado de la interfaz",
+ "reset-scaling": "Restablecer el escalado de la interfaz"
+ },
+ "section": {
+ "description": "Personaliza la apariencia, el ambiente y el comportamiento de la interfaz.",
+ "label": "Apariencia"
+ },
+ "title": "Interfaz de usuario",
+ "tooltips": {
+ "description": "Activar o desactivar los avisos emergentes en toda la interfaz.",
+ "label": "Mostrar sugerencias"
+ }
}
},
"widgets": {
@@ -745,7 +790,11 @@
},
"file-picker": {
"select-folder": "Seleccionar carpeta",
- "select-file": "Seleccionar archivo"
+ "select-file": "Seleccionar archivo",
+ "cancel": "Cancelar",
+ "search-placeholder": "Buscar archivos y carpetas...",
+ "select-current": "Seleccionar actual",
+ "title": "Selector de archivos"
},
"datetime-tokens": {
"common": {
@@ -836,9 +885,9 @@
"search-placeholder": "Buscar widgets..."
},
"active-window": {
- "auto-hide": {
- "label": "Ocultar automáticamente",
- "description": "Ocultar automáticamente el widget cuando no hay ventana activa."
+ "hide-mode": {
+ "label": "Modo de ocultación",
+ "description": "Controla el comportamiento del widget cuando no hay ventana activa."
},
"show-app-icon": {
"label": "Mostrar icono de la aplicación",
@@ -851,6 +900,10 @@
"width": {
"description": "Controla el tamaño horizontal del widget.",
"label": "Ancho del widget"
+ },
+ "colorize-icons": {
+ "label": "Colorear iconos",
+ "description": "Aplicar colores del tema al icono de la ventana activa."
}
},
"system-monitor": {
@@ -981,9 +1034,9 @@
}
},
"media-mini": {
- "auto-hide": {
- "label": "Ocultar automáticamente",
- "description": "Ocultar automáticamente el widget cuando no se está reproduciendo ningún medio."
+ "hide-mode": {
+ "label": "Modo de ocultación",
+ "description": "Controla el comportamiento del widget cuando no se está reproduciendo ningún medio."
},
"show-album-art": {
"label": "Mostrar arte del álbum",
@@ -1044,6 +1097,16 @@
"only-same-output": {
"description": "Muestra solo las aplicaciones del resultado donde se encuentra la barra.",
"label": "Solo de la misma salida"
+ },
+ "colorize-icons": {
+ "label": "Colorear iconos",
+ "description": "Aplicar colores del tema a los iconos de la barra de tareas."
+ }
+ },
+ "tray": {
+ "colorize-icons": {
+ "label": "Colorear iconos",
+ "description": "Aplicar colores del tema a los iconos de la bandeja del sistema."
}
}
}
@@ -1213,7 +1276,8 @@
"density": {
"compact": "Compacta",
"default": "Predeterminada",
- "comfortable": "Cómoda"
+ "comfortable": "Cómoda",
+ "mini": "Mini"
}
},
"launcher": {
@@ -1238,6 +1302,11 @@
"bottom_right": "Inferior derecha",
"bottom_center": "Inferior central",
"top_center": "Superior central"
+ },
+ "quickSettingsStyle": {
+ "modern": "Moderno",
+ "classic": "Clásico",
+ "compact": "Compacto"
}
},
"osd": {
@@ -1297,6 +1366,11 @@
"microphone-input": "Entrada del micrófono",
"both": "Salida del sistema + entrada del micrófono"
}
+ },
+ "hide-modes": {
+ "hidden": "Ocultar cuando esté vacío",
+ "transparent": "Transparente cuando está vacío",
+ "visible": "Siempre visible"
}
},
"session-menu": {
@@ -1334,9 +1408,9 @@
"calculator-error": "Error"
},
"system": {
- "uptime": "Tiempo de actividad: {uptime}",
+ "uptime": "Actividad: {uptime}",
"welcome-back": "¡Bienvenido de nuevo,",
- "monitor-description": "{model} ({width}x{height})",
+ "monitor-description": "{model} ({width}x{height} @ {scale}x)",
"scaling-percentage": "{percentage}%",
"location-display": "{name} ({coordinates})",
"signal-strength": "{signal}%",
@@ -1359,6 +1433,78 @@
"restart": "Reiniciar",
"suspend": "Suspender"
},
+ "quickSettings": {
+ "notifications": {
+ "label": {
+ "enabled": "Notificaciones",
+ "disabled": "No molestar"
+ },
+ "tooltip": {
+ "action": "Clic izquierdo: Abrir historial de notificaciones\nClic derecho: Alternar No molestar"
+ }
+ },
+ "screenRecorder": {
+ "label": {
+ "recording": "Detener",
+ "stopped": "Grabar"
+ },
+ "tooltip": {
+ "action": "Hacer clic para iniciar/detener la grabación de pantalla"
+ }
+ },
+ "powerProfile": {
+ "label": {
+ "unavailable": "Perfil de energía"
+ },
+ "tooltip": {
+ "action": "Hacer clic para cambiar el perfil de energía"
+ }
+ },
+ "wifi": {
+ "label": {
+ "ethernet": "Ethernet",
+ "wifi": "Wi-Fi",
+ "disconnected": "Wi-Fi desconectado"
+ },
+ "tooltip": {
+ "action": "Hacer clic para gestionar las conexiones Wi-Fi"
+ }
+ },
+ "bluetooth": {
+ "label": {
+ "enabled": "Bluetooth",
+ "disabled": "Bluetooth"
+ },
+ "tooltip": {
+ "action": "Hacer clic para gestionar los dispositivos Bluetooth"
+ }
+ },
+ "nightLight": {
+ "label": {
+ "enabled": "Luz nocturna",
+ "forced": "Luz nocturna",
+ "disabled": "Luz nocturna"
+ },
+ "tooltip": {
+ "action": "Hacer clic para alternar el modo Luz nocturna\nClic derecho: Abrir configuración"
+ }
+ },
+ "wallpaperSelector": {
+ "label": "Fondo de pantalla",
+ "tooltip": {
+ "action": "Clic izquierdo: Abrir selector de fondo de pantalla\nClic derecho: Establecer fondo de pantalla aleatorio"
+ }
+ },
+ "keepAwake": {
+ "label": {
+ "enabled": "Mantener despierto",
+ "disabled": "Mantener despierto"
+ },
+ "tooltip": {
+ "action": "Hacer clic para alternar el modo Mantener despierto"
+ }
+ }
+ },
"toast": {
"night-light": {
"enabled": "Activada",
@@ -1406,6 +1552,9 @@
"enabled": "Activado",
"disabled": "Desactivado"
},
+ "kofi": {
+ "opened": "Página de Ko-fi abierta en tu navegador"
+ },
"do-not-disturb": {
"enabled": "'No molestar' activado",
"disabled": "'No molestar' desactivado",
diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json
index bf0b4aca..9c9d5491 100644
--- a/Assets/Translations/fr.json
+++ b/Assets/Translations/fr.json
@@ -13,32 +13,6 @@
},
"select-avatar": "Sélectionner une image d'avatar"
},
- "ui": {
- "section": {
- "label": "Interface utilisateur",
- "description": "Personnalisez l'apparence, l'ergonomie et le comportement de l'interface."
- },
- "tooltips": {
- "label": "Afficher les tooltips",
- "description": "Activer ou désactiver les tooltips dans toute l'interface."
- },
- "dim-desktop": {
- "label": "Assombrir le bureau",
- "description": "Assombrir le bureau lorsque des panneaux ou des menus sont ouverts."
- },
- "border-radius": {
- "label": "Rayon de bordure",
- "description": "Contrôle l'arrondi des coins des fenêtres, des boutons et d'autres éléments."
- },
- "animation-speed": {
- "label": "Vitesse d'animation",
- "description": "Ajustez la vitesse globale des animations."
- },
- "animation-disable": {
- "label": "Désactiver les animations de l'interface",
- "description": "Désactiver toutes les animations pour une expérience plus rapide et réactive."
- }
- },
"screen-corners": {
"section": {
"label": "Coins de l'écran",
@@ -54,7 +28,8 @@
},
"radius": {
"label": "Rayon des coins de l'écran",
- "description": "Ajustez l'arrondi des coins de l'écran."
+ "description": "Ajustez l'arrondi des coins de l'écran.",
+ "reset": "Réinitialiser le rayon des coins de l'écran"
}
},
"fonts": {
@@ -83,16 +58,6 @@
}
},
"reset-scaling": "Réinitialiser l'échelle"
- },
- "control-center": {
- "section": {
- "label": "Centre de contrôle",
- "description": "Configurer le positionnement et le comportement du panneau du centre de contrôle."
- },
- "position": {
- "label": "Position",
- "description": "Choisissez où apparaît le panneau du centre de contrôle lors de l'ouverture."
- }
}
},
"audio": {
@@ -179,11 +144,9 @@
"monitors": {
"section": {
"label": "Paramètres par moniteur",
- "description": "Ajustez la mise à l'échelle et la luminosité pour chaque écran."
+ "description": "Ajustez la luminosité pour chaque écran."
},
- "scale": "Mise à l'échelle",
"brightness": "Luminosité",
- "reset-scaling": "Réinitialiser la mise à l'échelle",
"brightness-step": {
"label": "Incrément de luminosité",
"description": "Ajustez l'incrément pour les changements de luminosité (molette de la souris et raccourcis clavier)."
@@ -259,14 +222,21 @@
"widgets": {
"section": {
"label": "Positionnement des widgets",
- "description": "Glissez-déposez les widgets pour les réorganiser dans chaque section, ou utilisez les boutons ajouter/supprimer pour gérer les widgets."
+ "description": "Faites glisser et déposez les widgets pour les réorganiser. Les badges indiquent l'utilisation : [G]auche, [C]entre, [D]roite."
}
},
"monitors": {
"section": {
- "label": "Affichage sur les moniteur",
+ "label": "Affichage sur les moniteurs",
"description": "Afficher la barre sur des moniteurs spécifiques. Par défaut, sur tous si aucun n'est choisi."
}
+ },
+ "tray": {
+ "blacklist": {
+ "label": "Liste noire",
+ "description": "Ajouter des règles d'exclusion pour la boîte à miniatures, prend en charge les caractères génériques (*).",
+ "placeholder": "ex: nm-applet, Fcitx*"
+ }
}
},
"dock": {
@@ -290,6 +260,10 @@
"floating-distance": {
"label": "Distance de flottaison du dock",
"description": "Ajustez la distance de flottaison par rapport au bord de l'écran."
+ },
+ "colorize-icons": {
+ "label": "Coloriser les icônes",
+ "description": "Appliquer les couleurs du thème aux icônes d'applications du dock (applications non focalisées uniquement)."
}
},
"monitors": {
@@ -402,6 +376,10 @@
"label": "Activer l'affichage à l'écran",
"description": "Afficher en temps réel les changements de volume et de luminosité."
},
+ "always-on-top": {
+ "label": "Toujours au premier plan",
+ "description": "Afficher l'OSD au-dessus des fenêtres plein écran et autres couches."
+ },
"location": {
"label": "Emplacement",
"description": "Emplacement des affichages à l'écran."
@@ -529,6 +507,9 @@
},
"qt": {
"description": "Écrire {filepath}"
+ },
+ "kcolorscheme": {
+ "description": "Écrire {filepath}"
}
},
"terminal": {
@@ -554,9 +535,9 @@
"description": "Écrire ~/.config/fuzzel/themes/noctalia et recharger",
"description-missing": "Nécessite que le lanceur fuzzel soit installé"
},
- "vesktop": {
- "description": "Écrire ~/.config/vesktop/themes/noctalia.theme.css",
- "description-missing": "Nécessite que le client Discord vesktop soit installé"
+ "discord": {
+ "description": "Écrire {filepath} pour {client}",
+ "description-missing": "Aucun client Discord détecté. Installez vesktop, webcord, armcord, equibop, lightcord ou dorion."
},
"pywalfox": {
"description": "Écrire ~/.cache/wal/colors.json et exécuter pywalfox update",
@@ -703,7 +684,8 @@
"description": "Un grand merci à notre {count} super contributeur !",
"description_plural": "Un grand merci à nos {count} super contributeurs !"
}
- }
+ },
+ "support": "Soutenez-nous"
},
"hooks": {
"title": "Hooks",
@@ -737,6 +719,69 @@
"description": "• Hook Fond d'écran : $1 = chemin du fond d'écran, $2 = nom de l'écran\n• Hook de bascule de thème : $1 = true/false (état du mode sombre)"
}
}
+ },
+ "control-center": {
+ "position": {
+ "description": "Choisissez où le panneau du Centre de contrôle apparaît lorsqu'il est ouvert.",
+ "label": "Position"
+ },
+ "section": {
+ "description": "Configurer le positionnement et le comportement du panneau du Centre de contrôle.",
+ "label": "Apparence"
+ },
+ "title": "Centre de contrôle",
+ "cards": {
+ "section": {
+ "description": "Personnalisez les commandes qui apparaissent dans le Centre de contrôle et leur ordre d'affichage.",
+ "label": "Cartes"
+ }
+ },
+ "shortcuts": {
+ "section": {
+ "description": "Configurer et gérer les widgets de raccourcis.",
+ "label": "Widgets de raccourcis"
+ },
+ "sectionLeft": "Gauche",
+ "sectionRight": "Droite"
+ }
+ },
+ "user-interface": {
+ "animation-disable": {
+ "description": "Désactiver toutes les animations pour une expérience plus rapide et plus réactive.",
+ "label": "Désactiver les animations de l'interface utilisateur"
+ },
+ "animation-speed": {
+ "description": "Ajuster la vitesse globale de l'animation.",
+ "label": "Vitesse d'animation",
+ "reset": "Réinitialiser la vitesse de l'animation"
+ },
+ "border-radius": {
+ "description": "Contrôle l'arrondi des angles des fenêtres, des boutons et d'autres éléments.",
+ "label": "Rayon de bordure",
+ "reset": "Réinitialiser le rayon de la bordure"
+ },
+ "compact-lockscreen": {
+ "description": "Afficher uniquement le champ de saisie de connexion et les commandes système, en masquant les widgets météo et multimédia.",
+ "label": "Écran de verrouillage compact"
+ },
+ "dim-desktop": {
+ "description": "Atténuer le bureau lorsque des panneaux ou des menus sont ouverts.",
+ "label": "Dim bureau"
+ },
+ "scaling": {
+ "description": "Modifie la taille de l'interface utilisateur générale, à l'exception de la barre.",
+ "label": "Mise à l'échelle de l'interface",
+ "reset-scaling": "Réinitialiser l'échelle de l'interface"
+ },
+ "section": {
+ "description": "Personnaliser l'apparence, l'ergonomie et le comportement de l'interface.",
+ "label": "Apparence"
+ },
+ "title": "Interface utilisateur",
+ "tooltips": {
+ "description": "Activer ou désactiver les info-bulles dans toute l'interface.",
+ "label": "Afficher les infobulles"
+ }
}
},
"widgets": {
@@ -745,7 +790,11 @@
},
"file-picker": {
"select-folder": "Sélectionner un dossier",
- "select-file": "Sélectionner un fichier"
+ "select-file": "Sélectionner un fichier",
+ "cancel": "Annuler",
+ "search-placeholder": "Rechercher des fichiers et des dossiers...",
+ "select-current": "Sélectionner Actuel",
+ "title": "Sélecteur de fichiers"
},
"datetime-tokens": {
"common": {
@@ -844,13 +893,17 @@
"label": "Mode de défilement",
"description": "Contrôler quand le défilement de texte est activé pour les titres de fenêtre longs."
},
- "auto-hide": {
- "label": "Masquer automatiquement",
- "description": "Masquer automatiquement le widget quand aucune fenêtre n'est active."
+ "hide-mode": {
+ "label": "Mode de masquage",
+ "description": "Contrôle le comportement du widget lorsqu'aucune fenêtre n'est active."
},
"width": {
"description": "Contrôle la taille horizontale du widget.",
"label": "Largeur du widget"
+ },
+ "colorize-icons": {
+ "label": "Coloriser les icônes",
+ "description": "Appliquer les couleurs du thème à l'icône de la fenêtre active."
}
},
"system-monitor": {
@@ -997,9 +1050,9 @@
"label": "Mode de défilement",
"description": "Contrôler quand le défilement de texte est activé pour les titres de piste longs."
},
- "auto-hide": {
- "label": "Masquer automatiquement",
- "description": "Masquer automatiquement le widget quand aucun média n'est en cours de lecture."
+ "hide-mode": {
+ "label": "Mode de masquage",
+ "description": "Contrôle le comportement du widget lorsqu'aucun média n'est en cours de lecture."
},
"no-active-player": "Aucun lecteur actif"
},
@@ -1044,6 +1097,16 @@
"only-same-output": {
"description": "Afficher uniquement les applications de la sortie où la barre est située.",
"label": "Seulement à partir de la même sortie"
+ },
+ "colorize-icons": {
+ "label": "Coloriser les icônes",
+ "description": "Appliquer les couleurs du thème aux icônes de la barre des tâches."
+ }
+ },
+ "tray": {
+ "colorize-icons": {
+ "label": "Coloriser les icônes",
+ "description": "Appliquer les couleurs du thème aux icônes de la barre système."
}
}
}
@@ -1213,7 +1276,8 @@
"density": {
"compact": "Compact",
"default": "Défaut",
- "comfortable": "Confortable"
+ "comfortable": "Confortable",
+ "mini": "Mini"
}
},
"launcher": {
@@ -1238,6 +1302,11 @@
"bottom_right": "En bas à droite",
"bottom_center": "En bas au centre",
"top_center": "En haut au centre"
+ },
+ "quickSettingsStyle": {
+ "modern": "Moderne",
+ "classic": "Classique",
+ "compact": "Compact"
}
},
"osd": {
@@ -1297,6 +1366,11 @@
"microphone-input": "Entrée microphone",
"both": "Sortie système + entrée microphone"
}
+ },
+ "hide-modes": {
+ "hidden": "Masquer si vide",
+ "transparent": "Transparent quand vide",
+ "visible": "Toujours visible"
}
},
"session-menu": {
@@ -1334,9 +1408,8 @@
"calculator-error": "Erreur"
},
"system": {
- "uptime": "Temps d'activité : {uptime}",
"welcome-back": "Bon retour,",
- "monitor-description": "{model} ({width}x{height})",
+ "monitor-description": "{model} ({width}x{height} @ {scale}x)",
"scaling-percentage": "{percentage}%",
"location-display": "{name} ({coordinates})",
"signal-strength": "{signal}%",
@@ -1348,7 +1421,8 @@
"user-requested": "Demandé par l'utilisateur",
"unknown": "Inconnu",
"unknown-version": "Inconnue",
- "unknown-layout": "Inconnue"
+ "unknown-layout": "Inconnue",
+ "uptime": "Activité : {uptime}"
},
"lock-screen": {
"password": "Entrez votre mot de passe...",
@@ -1359,6 +1433,78 @@
"restart": "Redémarrer",
"suspend": "Mettre en veille"
},
+ "quickSettings": {
+ "notifications": {
+ "label": {
+ "enabled": "Notifications",
+ "disabled": "Ne pas déranger"
+ },
+ "tooltip": {
+ "action": "Clic gauche : Ouvrir l'historique des notifications\nClic droit : Basculer Ne pas déranger"
+ }
+ },
+ "screenRecorder": {
+ "label": {
+ "recording": "Arrêter",
+ "stopped": "Enregistrer"
+ },
+ "tooltip": {
+ "action": "Cliquer pour démarrer/arrêter l'enregistrement d'écran"
+ }
+ },
+ "powerProfile": {
+ "label": {
+ "unavailable": "Profil d'alimentation"
+ },
+ "tooltip": {
+ "action": "Cliquer pour changer de profil d'alimentation"
+ }
+ },
+ "wifi": {
+ "label": {
+ "ethernet": "Ethernet",
+ "wifi": "Wi-Fi",
+ "disconnected": "Wi-Fi déconnecté"
+ },
+ "tooltip": {
+ "action": "Cliquer pour gérer les connexions Wi-Fi"
+ }
+ },
+ "bluetooth": {
+ "label": {
+ "enabled": "Bluetooth",
+ "disabled": "Bluetooth"
+ },
+ "tooltip": {
+ "action": "Cliquer pour gérer les appareils Bluetooth"
+ }
+ },
+ "nightLight": {
+ "label": {
+ "enabled": "Lumière nocturne",
+ "forced": "Lumière nocturne",
+ "disabled": "Lumière nocturne"
+ },
+ "tooltip": {
+ "action": "Cliquer pour basculer le mode Lumière nocturne\nClic droit : Ouvrir les paramètres"
+ }
+ },
+ "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"
+ }
+ },
+ "keepAwake": {
+ "label": {
+ "enabled": "Rester éveillé",
+ "disabled": "Rester éveillé"
+ },
+ "tooltip": {
+ "action": "Cliquer pour basculer le mode Rester éveillé"
+ }
+ }
+ },
"toast": {
"night-light": {
"enabled": "Activé",
@@ -1406,6 +1552,9 @@
"enabled": "Activé",
"disabled": "Désactivé"
},
+ "kofi": {
+ "opened": "Page Ko-fi ouverte dans votre navigateur"
+ },
"do-not-disturb": {
"enabled": "'Ne pas déranger' activé",
"disabled": "'Ne pas déranger' désactivé",
diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json
index c1895420..a4d968ae 100644
--- a/Assets/Translations/pt.json
+++ b/Assets/Translations/pt.json
@@ -13,32 +13,6 @@
},
"select-avatar": "Selecionar imagem de avatar"
},
- "ui": {
- "section": {
- "label": "Interface do usuário",
- "description": "Personalize a aparência, a sensação e o comportamento da interface."
- },
- "tooltips": {
- "label": "Mostrar tooltips",
- "description": "Ativar ou desativar tooltips em toda a interface."
- },
- "dim-desktop": {
- "label": "Escurecer área de trabalho",
- "description": "Escurece a área de trabalho quando painéis ou menus estão abertos."
- },
- "border-radius": {
- "label": "Raio da borda",
- "description": "Controla o arredondamento dos cantos de janelas, botões e outros elementos."
- },
- "animation-speed": {
- "label": "Velocidade da animação",
- "description": "Ajuste a velocidade global da animação."
- },
- "animation-disable": {
- "label": "Desativar animações da interface",
- "description": "Desativar todas as animações para uma experiência mais rápida e responsiva."
- }
- },
"screen-corners": {
"section": {
"label": "Cantos da tela",
@@ -54,7 +28,8 @@
},
"radius": {
"label": "Raio dos cantos da tela",
- "description": "Ajuste os cantos arredondados da tela."
+ "description": "Ajuste os cantos arredondados da tela.",
+ "reset": "Redefinir raio dos cantos da tela"
}
},
"fonts": {
@@ -83,16 +58,6 @@
}
},
"reset-scaling": "Redefinir escala"
- },
- "control-center": {
- "section": {
- "label": "Centro de controle",
- "description": "Configurar o posicionamento e comportamento do painel do centro de controle."
- },
- "position": {
- "label": "Posição",
- "description": "Escolha onde o painel do centro de controle aparece quando aberto."
- }
}
},
"audio": {
@@ -179,11 +144,9 @@
"monitors": {
"section": {
"label": "Configurações por monitor",
- "description": "Ajuste a escala e o brilho para cada tela."
+ "description": "Ajustar o brilho das configurações para cada tela."
},
- "scale": "Escala",
"brightness": "Brilho",
- "reset-scaling": "Redefinir escala",
"brightness-step": {
"label": "Tamanho do passo do brilho",
"description": "Ajuste o tamanho do passo para alterações de brilho (roda do mouse e atalhos de teclado)."
@@ -259,7 +222,7 @@
"widgets": {
"section": {
"label": "Posicionamento dos widgets",
- "description": "Arraste e solte os widgets para reordená-los em cada seção, ou use os botões de adicionar/remover para gerenciar os widgets."
+ "description": "Arraste e solte widgets para reordená-los. Os emblemas indicam o uso: [E]squerda, [C]entro, [D]ireita."
}
},
"monitors": {
@@ -267,6 +230,13 @@
"label": "Exibição nos monitores",
"description": "Mostra a barra em monitores específicos. O padrão é todos, se nenhum for escolhido."
}
+ },
+ "tray": {
+ "blacklist": {
+ "label": "Lista Negra",
+ "description": "Adicione regras de exclusão para a bandeja do sistema, suporta curingas (*).",
+ "placeholder": "ex: nm-applet, Fcitx*"
+ }
}
},
"dock": {
@@ -290,6 +260,10 @@
"floating-distance": {
"label": "Distância de flutuação da dock",
"description": "Ajuste a distância de flutuação da borda da tela."
+ },
+ "colorize-icons": {
+ "label": "Colorir ícones",
+ "description": "Aplicar cores do tema aos ícones de aplicativos da dock (apenas aplicativos não focados)."
}
},
"monitors": {
@@ -495,6 +469,9 @@
},
"qt": {
"description": "Escrever {filepath}"
+ },
+ "kcolorscheme": {
+ "description": "Escrever {filepath}"
}
},
"terminal": {
@@ -520,9 +497,9 @@
"description": "Escrever {filepath} e recarregar",
"description-missing": "Requer que o {app} esteja instalado"
},
- "vesktop": {
- "description": "Escrever {filepath}",
- "description-missing": "Requer que o {app} esteja instalado"
+ "discord": {
+ "description": "Escrever {filepath} para {client}",
+ "description-missing": "Nenhum cliente Discord detectado. Instale vesktop, webcord, armcord, equibop, lightcord ou dorion."
},
"pywalfox": {
"description": "Escrever {filepath} e executar pywalfox update",
@@ -669,7 +646,8 @@
"description": "Agradecimentos ao nosso incrível colaborador!",
"description_plural": "Agradecimentos aos nossos {count} incríveis colaboradores!"
}
- }
+ },
+ "support": "Apoie-nos"
},
"hooks": {
"title": "Hooks",
@@ -717,6 +695,10 @@
"label": "Ativar exibição na tela",
"description": "Mostrar alterações de volume e brilho em tempo real."
},
+ "always-on-top": {
+ "label": "Sempre no topo",
+ "description": "Exibir OSD acima de janelas em tela cheia e outras camadas."
+ },
"location": {
"label": "Localização",
"description": "Onde a exibição na tela aparece."
@@ -737,6 +719,69 @@
"description": "Mostrar a OSD em monitores específicos. Padrão é todos se nenhum for escolhido."
}
}
+ },
+ "control-center": {
+ "position": {
+ "description": "Escolha onde o painel da Central de Controle aparece quando aberto.",
+ "label": "Posição"
+ },
+ "section": {
+ "description": "Configurar o posicionamento e o comportamento do painel da Central de Controle.",
+ "label": "Aparência"
+ },
+ "title": "Central de Controle",
+ "cards": {
+ "section": {
+ "description": "Personalize quais controles aparecem na Central de Controle e em que ordem.",
+ "label": "Cartas"
+ }
+ },
+ "shortcuts": {
+ "section": {
+ "description": "Configure e gerencie os widgets de atalhos.",
+ "label": "Widgets de atalhos"
+ },
+ "sectionLeft": "Esquerda",
+ "sectionRight": "Direito/Certo/À direita"
+ }
+ },
+ "user-interface": {
+ "animation-disable": {
+ "description": "Desative todas as animações para uma experiência mais rápida e responsiva.",
+ "label": "Desativar animações da interface do usuário"
+ },
+ "animation-speed": {
+ "description": "Ajustar a velocidade global da animação.",
+ "label": "Velocidade da animação",
+ "reset": "Redefinir velocidade da animação"
+ },
+ "border-radius": {
+ "description": "Controla o arredondamento dos cantos de janelas, botões e outros elementos.",
+ "label": "Raio da borda",
+ "reset": "Redefinir raio da borda"
+ },
+ "compact-lockscreen": {
+ "description": "Mostrar apenas a entrada de login e os controles do sistema, ocultando widgets de clima e mídia.",
+ "label": "Tela de bloqueio compacta"
+ },
+ "dim-desktop": {
+ "description": "Escurecer a área de trabalho quando painéis ou menus estiverem abertos.",
+ "label": "Dim área de trabalho"
+ },
+ "scaling": {
+ "description": "Altera o tamanho da interface geral do usuário, excluindo a barra.",
+ "label": "Escalonamento da interface",
+ "reset-scaling": "Redefinir escala da interface"
+ },
+ "section": {
+ "description": "Personalize a aparência, a sensação e o comportamento da interface.",
+ "label": "Aparência"
+ },
+ "title": "Interface do usuário",
+ "tooltips": {
+ "description": "Ativar ou desativar dicas de ferramentas em toda a interface.",
+ "label": "Mostrar dicas de ferramenta"
+ }
}
},
"widgets": {
@@ -745,7 +790,11 @@
},
"file-picker": {
"select-folder": "Selecionar Pasta",
- "select-file": "Selecionar Arquivo"
+ "select-file": "Selecionar Arquivo",
+ "cancel": "Cancelar",
+ "search-placeholder": "Pesquisar arquivos e pastas...",
+ "select-current": "Selecionar Atual",
+ "title": "Seletor de Arquivos"
},
"datetime-tokens": {
"common": {
@@ -836,9 +885,9 @@
"search-placeholder": "Pesquisar widgets..."
},
"active-window": {
- "auto-hide": {
- "label": "Ocultar automaticamente",
- "description": "Ocultar automaticamente o widget quando nenhuma janela está ativa."
+ "hide-mode": {
+ "label": "Modo de ocultação",
+ "description": "Controla o comportamento do widget quando nenhuma janela está ativa."
},
"show-app-icon": {
"label": "Mostrar ícone do aplicativo",
@@ -851,6 +900,10 @@
"width": {
"description": "Controla o tamanho horizontal do widget.",
"label": "Largura do Widget"
+ },
+ "colorize-icons": {
+ "label": "Colorir ícones",
+ "description": "Aplicar cores do tema ao ícone da janela ativa."
}
},
"system-monitor": {
@@ -981,9 +1034,9 @@
}
},
"media-mini": {
- "auto-hide": {
- "label": "Ocultar automaticamente",
- "description": "Ocultar automaticamente o widget quando nenhuma mídia está sendo reproduzida."
+ "hide-mode": {
+ "label": "Modo de ocultação",
+ "description": "Controla o comportamento do widget quando nenhuma mídia está sendo reproduzida."
},
"show-album-art": {
"label": "Mostrar arte do álbum",
@@ -1044,6 +1097,16 @@
"only-same-output": {
"description": "Mostrar apenas os aplicativos da saída onde a barra está localizada.",
"label": "Apenas da mesma saída"
+ },
+ "colorize-icons": {
+ "label": "Colorir ícones",
+ "description": "Aplicar cores do tema aos ícones da barra de tarefas."
+ }
+ },
+ "tray": {
+ "colorize-icons": {
+ "label": "Colorir ícones",
+ "description": "Aplicar cores do tema aos ícones da bandeja do sistema."
}
}
}
@@ -1237,6 +1300,11 @@
"bottom_right": "Inferior direito",
"bottom_center": "Centro inferior",
"top_center": "Centro superior"
+ },
+ "quickSettingsStyle": {
+ "modern": "Moderno",
+ "classic": "Clássico",
+ "compact": "Compacto"
}
},
"bar": {
@@ -1249,7 +1317,8 @@
"density": {
"compact": "Compacta",
"default": "Padrão",
- "comfortable": "Confortável"
+ "comfortable": "Confortável",
+ "mini": "Mini"
}
},
"display-mode": {
@@ -1297,6 +1366,11 @@
"microphone-input": "Entrada do microfone",
"both": "Saída do sistema + entrada do microfone"
}
+ },
+ "hide-modes": {
+ "hidden": "Ocultar Quando Vazio",
+ "transparent": "Transparente quando vazio",
+ "visible": "Sempre Visível"
}
},
"session-menu": {
@@ -1334,9 +1408,9 @@
"calculator-error": "Erro"
},
"system": {
- "uptime": "Sistema ativo há: {uptime}",
+ "uptime": "Atividade: {uptime}",
"welcome-back": "Bem-vindo(a) de volta, {user}!",
- "monitor-description": "{model} ({width}x{height})",
+ "monitor-description": "{model} ({width}x{height} @ {scale}x)",
"scaling-percentage": "{percentage}%",
"location-display": "{name} ({coordinates})",
"signal-strength": "{signal}%",
@@ -1359,6 +1433,78 @@
"restart": "Reiniciar",
"suspend": "Suspender"
},
+ "quickSettings": {
+ "notifications": {
+ "label": {
+ "enabled": "Notificações",
+ "disabled": "Não perturbar"
+ },
+ "tooltip": {
+ "action": "Clique esquerdo: Abrir histórico de notificações\nClique direito: Alternar Não perturbar"
+ }
+ },
+ "screenRecorder": {
+ "label": {
+ "recording": "Parar",
+ "stopped": "Gravar"
+ },
+ "tooltip": {
+ "action": "Clique para iniciar/parar a gravação da tela"
+ }
+ },
+ "powerProfile": {
+ "label": {
+ "unavailable": "Perfil de energia"
+ },
+ "tooltip": {
+ "action": "Clique para alternar o perfil de energia"
+ }
+ },
+ "wifi": {
+ "label": {
+ "ethernet": "Ethernet",
+ "wifi": "Wi-Fi",
+ "disconnected": "Wi-Fi desconectado"
+ },
+ "tooltip": {
+ "action": "Clique para gerenciar conexões Wi-Fi"
+ }
+ },
+ "bluetooth": {
+ "label": {
+ "enabled": "Bluetooth",
+ "disabled": "Bluetooth"
+ },
+ "tooltip": {
+ "action": "Clique para gerenciar dispositivos Bluetooth"
+ }
+ },
+ "nightLight": {
+ "label": {
+ "enabled": "Luz noturna",
+ "forced": "Luz noturna",
+ "disabled": "Luz noturna"
+ },
+ "tooltip": {
+ "action": "Clique para alternar o modo Luz noturna\nClique direito: Abrir configurações"
+ }
+ },
+ "wallpaperSelector": {
+ "label": "Papel de parede",
+ "tooltip": {
+ "action": "Clique esquerdo: Abrir seletor de papel de parede\nClique direito: Definir papel de parede aleatório"
+ }
+ },
+ "keepAwake": {
+ "label": {
+ "enabled": "Manter acordado",
+ "disabled": "Manter acordado"
+ },
+ "tooltip": {
+ "action": "Clique para alternar o modo Manter acordado"
+ }
+ }
+ },
"toast": {
"night-light": {
"enabled": "Ativada",
@@ -1406,6 +1552,9 @@
"enabled": "Ativado",
"disabled": "Desativado"
},
+ "kofi": {
+ "opened": "Página do Ko-fi aberta no seu navegador"
+ },
"do-not-disturb": {
"enabled": "'Não perturbe' ativado",
"disabled": "'Não perturbe' desativado",
diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json
index 7906e01e..213be9cd 100644
--- a/Assets/Translations/zh-CN.json
+++ b/Assets/Translations/zh-CN.json
@@ -13,32 +13,6 @@
},
"select-avatar": "选择头像图片"
},
- "ui": {
- "section": {
- "label": "用户界面",
- "description": "自定义界面的外观、风格和行为。"
- },
- "tooltips": {
- "label": "显示工具提示",
- "description": "在整个界面中启用或禁用工具提示。"
- },
- "dim-desktop": {
- "label": "调暗桌面",
- "description": "当面板或菜单打开时调暗桌面。"
- },
- "border-radius": {
- "label": "边框圆角",
- "description": "控制窗口、按钮及其他元素的边角圆度。"
- },
- "animation-speed": {
- "label": "动画速度",
- "description": "调整全局动画速度。"
- },
- "animation-disable": {
- "label": "关闭动画",
- "description": "禁用所有动画效果,以获得更快速、更灵敏的体验。"
- }
- },
"screen-corners": {
"section": {
"label": "屏幕边角",
@@ -54,7 +28,8 @@
},
"radius": {
"label": "屏幕边角半径",
- "description": "调整屏幕圆角的弧度。"
+ "description": "调整屏幕圆角的弧度。",
+ "reset": "重置屏幕圆角半径"
}
},
"fonts": {
@@ -83,16 +58,6 @@
"description": "增大或减小等宽文本的尺寸"
}
}
- },
- "control-center": {
- "section": {
- "label": "控制中心",
- "description": "配置控制中心面板的定位和行为。"
- },
- "position": {
- "label": "位置",
- "description": "选择控制中心面板打开时出现的位置。"
- }
}
},
"audio": {
@@ -179,11 +144,9 @@
"monitors": {
"section": {
"label": "显示器设置",
- "description": "调整每个显示器的缩放比例和亮度。"
+ "description": "调整每个显示器的亮度设置。"
},
- "scale": "缩放比例",
"brightness": "亮度",
- "reset-scaling": "重置缩放",
"brightness-step": {
"label": "亮度步长",
"description": "调整亮度变化的步长(滚轮和键盘快捷键)。"
@@ -259,7 +222,7 @@
"widgets": {
"section": {
"label": "小部件定位",
- "description": "拖放小部件以在每个部分内重新排序,或使用添加/删除按钮管理小部件。"
+ "description": "拖放小部件以重新排序。徽章表示使用情况:[L]左、[C]中、[R]右。"
}
},
"monitors": {
@@ -267,6 +230,13 @@
"label": "显示器显示",
"description": "在特定显示器上显示状态栏。如果未选择,则默认为全部。"
}
+ },
+ "tray": {
+ "blacklist": {
+ "label": "黑名单",
+ "description": "添加托盘排除规则,支持通配符 (*)。",
+ "placeholder": "例如:nm-applet, Fcitx*"
+ }
}
},
"dock": {
@@ -290,6 +260,10 @@
"floating-distance": {
"label": "Dock 浮动距离",
"description": "调整距离屏幕边缘的浮动距离。"
+ },
+ "colorize-icons": {
+ "label": "着色图标",
+ "description": "将主题颜色应用到 Dock 应用图标(仅限非聚焦应用)。"
}
},
"monitors": {
@@ -402,6 +376,10 @@
"label": "启用屏幕显示",
"description": "实时显示音量与亮度变化。"
},
+ "always-on-top": {
+ "label": "始终置顶",
+ "description": "在全屏窗口和其他图层之上显示OSD。"
+ },
"location": {
"label": "位置",
"description": "屏幕显示出现的位置。"
@@ -529,6 +507,9 @@
},
"qt": {
"description": "写入 {filepath}"
+ },
+ "kcolorscheme": {
+ "description": "写入 {filepath}"
}
},
"terminal": {
@@ -554,9 +535,9 @@
"description": "写入 {filepath} 并重新加载",
"description-missing": "需要安装 {app}"
},
- "vesktop": {
- "description": "写入 {filepath}",
- "description-missing": "需要安装 {app}"
+ "discord": {
+ "description": "为 {client} 写入 {filepath}",
+ "description-missing": "未检测到 Discord 客户端。请安装 vesktop、webcord、armcord、equibop、lightcord 或 dorion。"
},
"pywalfox": {
"description": "写入 {filepath} 并运行 pywalfox update",
@@ -703,7 +684,8 @@
"description": "向我们 {count} 位超棒的贡献者致敬!",
"description_plural": "向我们 {count} 位超棒的贡献者致敬!"
}
- }
+ },
+ "support": "支持我们"
},
"hooks": {
"title": "钩子",
@@ -737,6 +719,69 @@
"description": "• 壁纸钩子: $1 = 壁纸路径, $2 = 屏幕名称\n• 主题切换钩子: $1 = true/false (深色模式状态)"
}
}
+ },
+ "control-center": {
+ "position": {
+ "description": "选择打开控制中心时面板出现的位置。",
+ "label": "位置"
+ },
+ "section": {
+ "description": "配置控制中心面板的位置和行为。",
+ "label": "外观"
+ },
+ "title": "控制中心",
+ "cards": {
+ "section": {
+ "description": "自定义在控制中心显示的控制项及其顺序。",
+ "label": "卡片"
+ }
+ },
+ "shortcuts": {
+ "section": {
+ "description": "配置和管理快捷方式小部件。",
+ "label": "快捷方式小部件"
+ },
+ "sectionLeft": "左",
+ "sectionRight": "右"
+ }
+ },
+ "user-interface": {
+ "animation-disable": {
+ "description": "禁用所有动画以获得更快、更流畅的体验。",
+ "label": "禁用 UI 动画"
+ },
+ "animation-speed": {
+ "description": "调整全局动画速度。",
+ "label": "动画速度",
+ "reset": "重置动画速度"
+ },
+ "border-radius": {
+ "description": "控制窗口、按钮和其他元素的圆角程度。",
+ "label": "边框半径",
+ "reset": "重置边框半径"
+ },
+ "compact-lockscreen": {
+ "description": "仅显示登录输入和系统控制,隐藏天气和媒体小部件。",
+ "label": "紧凑型锁屏"
+ },
+ "dim-desktop": {
+ "description": "当面板或菜单打开时,桌面变暗。",
+ "label": "昏暗的桌面"
+ },
+ "scaling": {
+ "description": "更改通用用户界面大小,不包括栏。",
+ "label": "界面缩放",
+ "reset-scaling": "重置界面缩放"
+ },
+ "section": {
+ "description": "自定义界面的外观、感觉和行为。",
+ "label": "外观"
+ },
+ "title": "用户界面",
+ "tooltips": {
+ "description": "启用或禁用整个界面的工具提示。",
+ "label": "显示工具提示"
+ }
}
},
"widgets": {
@@ -745,7 +790,11 @@
},
"file-picker": {
"select-folder": "选择文件夹",
- "select-file": "选择文件"
+ "select-file": "选择文件",
+ "cancel": "取消",
+ "search-placeholder": "搜索文件和文件夹...",
+ "select-current": "选择当前",
+ "title": "文件选择器"
},
"datetime-tokens": {
"common": {
@@ -836,9 +885,9 @@
"search-placeholder": "搜索小部件..."
},
"active-window": {
- "auto-hide": {
- "label": "自动隐藏",
- "description": "当没有活动窗口时自动隐藏小部件。"
+ "hide-mode": {
+ "label": "隐藏模式",
+ "description": "控制当没有活动窗口时小部件的行为。"
},
"show-app-icon": {
"label": "显示应用图标",
@@ -851,6 +900,10 @@
"width": {
"description": "控制小部件的水平尺寸。",
"label": "小部件宽度"
+ },
+ "colorize-icons": {
+ "label": "着色图标",
+ "description": "将主题颜色应用到活动窗口图标。"
}
},
"system-monitor": {
@@ -981,9 +1034,9 @@
}
},
"media-mini": {
- "auto-hide": {
- "label": "自动隐藏",
- "description": "当没有媒体播放时自动隐藏小部件。"
+ "hide-mode": {
+ "label": "隐藏模式",
+ "description": "控制当没有媒体播放时小部件的行为。"
},
"show-album-art": {
"label": "显示专辑封面",
@@ -1044,6 +1097,16 @@
"only-same-output": {
"label": "仅显示同屏幕",
"description": "仅显示任务栏所在屏幕上的应用程序"
+ },
+ "colorize-icons": {
+ "label": "着色图标",
+ "description": "将主题颜色应用到任务栏图标。"
+ }
+ },
+ "tray": {
+ "colorize-icons": {
+ "label": "着色图标",
+ "description": "将主题颜色应用到系统托盘图标。"
}
}
}
@@ -1213,7 +1276,8 @@
"density": {
"compact": "紧凑",
"default": "默认",
- "comfortable": "舒适"
+ "comfortable": "舒适",
+ "mini": "迷你"
}
},
"launcher": {
@@ -1238,6 +1302,11 @@
"bottom_right": "右下角",
"bottom_center": "底部居中",
"top_center": "顶部居中"
+ },
+ "quickSettingsStyle": {
+ "modern": "现代",
+ "classic": "经典",
+ "compact": "紧凑"
}
},
"osd": {
@@ -1297,6 +1366,11 @@
"microphone-input": "麦克风输入",
"both": "系统输出 + 麦克风输入"
}
+ },
+ "hide-modes": {
+ "hidden": "当为空时隐藏",
+ "transparent": "空时透明",
+ "visible": "始终可见"
}
},
"session-menu": {
@@ -1336,7 +1410,7 @@
"system": {
"uptime": "系统运行时间:{uptime}",
"welcome-back": "欢迎回来,",
- "monitor-description": "{model} ({width}x{height})",
+ "monitor-description": "{model} ({width}x{height} @ {scale}x)",
"scaling-percentage": "{percentage}%",
"location-display": "{name} ({coordinates})",
"signal-strength": "{signal}%",
@@ -1359,6 +1433,78 @@
"restart": "重启",
"suspend": "挂起"
},
+ "quickSettings": {
+ "notifications": {
+ "label": {
+ "enabled": "通知",
+ "disabled": "勿扰模式"
+ },
+ "tooltip": {
+ "action": "左键:打开通知历史\n右键:切换勿扰模式"
+ }
+ },
+ "screenRecorder": {
+ "label": {
+ "recording": "停止",
+ "stopped": "录制"
+ },
+ "tooltip": {
+ "action": "点击开始/停止屏幕录制"
+ }
+ },
+ "powerProfile": {
+ "label": {
+ "unavailable": "电源模式"
+ },
+ "tooltip": {
+ "action": "点击切换电源模式"
+ }
+ },
+ "wifi": {
+ "label": {
+ "ethernet": "以太网",
+ "wifi": "Wi-Fi",
+ "disconnected": "Wi-Fi 已断开"
+ },
+ "tooltip": {
+ "action": "点击管理 Wi-Fi 连接"
+ }
+ },
+ "bluetooth": {
+ "label": {
+ "enabled": "蓝牙",
+ "disabled": "蓝牙"
+ },
+ "tooltip": {
+ "action": "点击管理蓝牙设备"
+ }
+ },
+ "nightLight": {
+ "label": {
+ "enabled": "夜间模式",
+ "forced": "夜间模式",
+ "disabled": "夜间模式"
+ },
+ "tooltip": {
+ "action": "点击切换夜间模式\n右键:打开设置"
+ }
+ },
+ "wallpaperSelector": {
+ "label": "壁纸",
+ "tooltip": {
+ "action": "左键:打开壁纸选择器\n右键:设置随机壁纸"
+ }
+ },
+ "keepAwake": {
+ "label": {
+ "enabled": "保持唤醒",
+ "disabled": "保持唤醒"
+ },
+ "tooltip": {
+ "action": "点击切换保持唤醒模式"
+ }
+ }
+ },
"toast": {
"night-light": {
"enabled": "已启用",
@@ -1406,6 +1552,9 @@
"enabled": "已启用",
"disabled": "已禁用"
},
+ "kofi": {
+ "opened": "Ko-fi页面已在您的浏览器中打开"
+ },
"do-not-disturb": {
"enabled": "'勿扰模式'已启用",
"disabled": "'勿扰模式'已禁用",
diff --git a/Assets/settings-default.json b/Assets/settings-default.json
index 7b03ea66..b8a82259 100644
--- a/Assets/settings-default.json
+++ b/Assets/settings-default.json
@@ -36,12 +36,6 @@
{
"id": "NotificationHistory"
},
- {
- "id": "WiFi"
- },
- {
- "id": "Bluetooth"
- },
{
"id": "Battery"
},
@@ -65,10 +59,12 @@
"dimDesktop": true,
"showScreenCorners": false,
"forceBlackScreenCorners": false,
+ "scaleRatio": 1,
"radiusRatio": 1,
"screenRadiusRatio": 1,
"animationSpeed": 1,
- "animationDisabled": false
+ "animationDisabled": false,
+ "compactLockScreen": false
},
"location": {
"name": "Tokyo",
@@ -112,7 +108,59 @@
"terminalCommand": "xterm -e"
},
"controlCenter": {
- "position": "close_to_bar_button"
+ "position": "close_to_bar_button",
+ "shortcuts": {
+ "left": [
+ {
+ "id": "WiFi"
+ },
+ {
+ "id": "Bluetooth"
+ },
+ {
+ "id": "ScreenRecorder"
+ },
+ {
+ "id": "WallpaperSelector"
+ }
+ ],
+ "right": [
+ {
+ "id": "Notifications"
+ },
+ {
+ "id": "PowerProfile"
+ },
+ {
+ "id": "KeepAwake"
+ },
+ {
+ "id": "NightLight"
+ }
+ ]
+ },
+ "cards": [
+ {
+ "enabled": true,
+ "id": "profile-card"
+ },
+ {
+ "enabled": true,
+ "id": "shortcuts-card"
+ },
+ {
+ "enabled": true,
+ "id": "audio-card"
+ },
+ {
+ "enabled": true,
+ "id": "weather-card"
+ },
+ {
+ "enabled": true,
+ "id": "media-sysmon-card"
+ }
+ ]
},
"dock": {
"displayMode": "always_visible",
@@ -120,7 +168,8 @@
"floatingRatio": 1,
"onlySameOutput": true,
"monitors": [],
- "pinnedApps": []
+ "pinnedApps": [],
+ "colorizeIcons": false
},
"network": {
"wifiEnabled": true
@@ -140,7 +189,8 @@
"enabled": true,
"location": "top_right",
"monitors": [],
- "autoHideMs": 2000
+ "autoHideMs": 2000,
+ "alwaysOnTop": false
},
"audio": {
"volumeStep": 5,
@@ -155,7 +205,6 @@
"fontFixed": "DejaVu Sans Mono",
"fontDefaultScale": 1,
"fontFixedScale": 1,
- "monitorsScaling": [],
"idleInhibitorEnabled": false,
"tooltipsEnabled": true
},
@@ -172,11 +221,18 @@
"templates": {
"gtk": false,
"qt": false,
+ "kcolorscheme": false,
"kitty": false,
"ghostty": false,
"foot": false,
"fuzzel": false,
- "vesktop": false,
+ "discord": false,
+ "discord_vesktop": false,
+ "discord_webcord": false,
+ "discord_armcord": false,
+ "discord_equibop": false,
+ "discord_lightcord": false,
+ "discord_dorion": false,
"pywalfox": false,
"enableUserTemplates": false
},
diff --git a/Bin/i18n-json-check.sh b/Bin/dev/i18n-json.sh
similarity index 74%
rename from Bin/i18n-json-check.sh
rename to Bin/dev/i18n-json.sh
index 408677b2..0db1585b 100755
--- a/Bin/i18n-json-check.sh
+++ b/Bin/dev/i18n-json.sh
@@ -123,13 +123,13 @@ EOF
# Make API call to Gemini
local api_url="https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${api_key}"
- print_color $BLUE " API URL: $api_url" >&2
+ # print_color $BLUE " API URL: $api_url" >&2
local response=$(curl -s -X POST "$api_url" \
-H "Content-Type: application/json" \
-d "$request_body" 2>/dev/null)
- print_color $BLUE " API Response: $response" >&2
+ # print_color $BLUE " API Response: $response" >&2
# Extract the translation from response - try multiple parsing approaches
local translation=$(echo "$response" | jq -r '.candidates[0].content.parts[0].text // .text // empty' 2>/dev/null | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
@@ -181,6 +181,40 @@ inject_translation() {
fi
}
+# Function to remove a key from JSON file using jq
+remove_json_key() {
+ local json_file=$1
+ local key_path=$2
+
+ # Split key path into array
+ local -a path_parts
+ IFS='.' read -ra path_parts <<< "$key_path"
+
+ # Build jq path array
+ local jq_path="["
+ for i in "${!path_parts[@]}"; do
+ if [[ $i -gt 0 ]]; then
+ jq_path+=","
+ fi
+ jq_path+="\"${path_parts[$i]}\""
+ done
+ jq_path+="]"
+
+ # Create a temporary file
+ local temp_file=$(mktemp)
+
+ # Use jq to delete the path
+ jq --argjson path "$jq_path" 'delpaths([$path])' "$json_file" > "$temp_file"
+
+ if [[ $? -eq 0 ]]; then
+ mv "$temp_file" "$json_file"
+ return 0
+ else
+ rm -f "$temp_file"
+ return 1
+ fi
+}
+
# Function to extract all keys from a JSON file recursively
extract_keys() {
local json_file=$1
@@ -207,6 +241,78 @@ extract_keys() {
' "$json_file" 2>/dev/null | sort
}
+# Function to extract empty keys from a JSON file recursively
+extract_empty_keys() {
+ local json_file=$1
+
+ if [[ ! -f "$json_file" ]]; then
+ echo "Error: File $json_file not found" >&2
+ return 1
+ fi
+
+ # Extract all keys with empty string or null values recursively using jq
+ jq -r '
+ def empty_keys_recursive:
+ if type == "object" then
+ keys[] as $k |
+ if (.[$k] | type) == "object" then
+ ($k + "." + (.[$k] | empty_keys_recursive))
+ elif (.[$k] == "" or .[$k] == null) then
+ $k
+ else
+ empty
+ end
+ else
+ empty
+ end;
+ empty_keys_recursive
+ ' "$json_file" 2>/dev/null | sort
+}
+
+# Function to remove empty objects recursively from JSON file
+remove_empty_objects() {
+ local json_file=$1
+
+ # Create a temporary file
+ local temp_file=$(mktemp)
+
+ # Use jq to recursively remove empty objects
+ # This function walks the entire JSON tree and removes any object that contains no leaf values
+ jq '
+ def remove_empty:
+ if type == "object" then
+ to_entries |
+ map(
+ .value |= remove_empty
+ ) |
+ map(
+ select(
+ .value != {} and
+ .value != [] and
+ .value != null and
+ .value != ""
+ )
+ ) |
+ from_entries |
+ if length == 0 then empty else . end
+ elif type == "array" then
+ map(remove_empty) |
+ map(select(. != null and . != {} and . != [] and . != ""))
+ else
+ .
+ end;
+ remove_empty
+ ' "$json_file" > "$temp_file" 2>/dev/null
+
+ if [[ $? -eq 0 ]]; then
+ mv "$temp_file" "$json_file"
+ return 0
+ else
+ rm -f "$temp_file"
+ return 1
+ fi
+}
+
# Function to get language files
get_language_files() {
find "$FOLDER_PATH" -maxdepth 1 -name "*.json" -type f | sort
@@ -223,15 +329,20 @@ generate_header() {
echo "Reference file: $REFERENCE_FILE"
echo "Folder: $(realpath "$FOLDER_PATH")"
if $TRANSLATE_MODE; then
- echo "Mode: TRANSLATION ENABLED"
+ echo "Mode: TRANSLATION ENABLED (translates missing keys, removes extra/empty keys and empty objects)"
fi
echo ""
echo "Notes:"
echo "- Keys are compared recursively through all nested JSON objects"
echo "- Missing keys indicate incomplete translations"
echo "- Extra keys might indicate deprecated keys or translation-specific additions"
+ echo "- Empty keys are keys with empty string (\"\") or null values"
+ echo "- Empty objects are nested objects containing no actual values (only other empty objects)"
echo "- Translation completion percentage is calculated based on English reference"
echo "- Results are sorted by descending line numbers for easier editing"
+ if $TRANSLATE_MODE; then
+ echo "- In translation mode, extra keys, empty keys, and empty objects are automatically removed"
+ fi
echo ""
echo "This report compares all language JSON files against the English reference file"
echo "and identifies missing keys and extra keys in each language."
@@ -427,11 +538,92 @@ compare_language() {
done
rm -f "$temp_extra"
echo ""
+
+ # Remove extra keys if in translate mode
+ if $TRANSLATE_MODE; then
+ print_color $BLUE "Removing extra keys from $lang_name..." >&2
+ local removed_count=0
+ local failed_removal_count=0
+
+ while IFS= read -r key; do
+ if [[ -n "$key" ]]; then
+ print_color $YELLOW " Removing: $key" >&2
+
+ if remove_json_key "$lang_file" "$key"; then
+ print_color $GREEN " ✓ Removed: $key" >&2
+ removed_count=$((removed_count + 1))
+ else
+ print_color $RED " ✗ Failed to remove: $key" >&2
+ failed_removal_count=$((failed_removal_count + 1))
+ fi
+ fi
+ done <<< "$extra_keys"
+
+ echo ""
+ print_color $GREEN "Removal complete: $removed_count removed, $failed_removal_count failed" >&2
+ echo ""
+ fi
else
echo "✅ No extra keys in $lang_name"
echo ""
fi
+ # Handle empty keys in translate mode
+ if $TRANSLATE_MODE; then
+ local empty_keys=$(extract_empty_keys "$lang_file")
+ local empty_count=$(count_non_empty_lines "$empty_keys")
+
+ if [[ $empty_count -gt 0 && -n "$empty_keys" ]]; then
+ echo "EMPTY KEYS IN $lang_name:"
+
+ # Display empty keys
+ local counter=1
+ while IFS= read -r key; do
+ if [[ -n "$key" ]]; then
+ local lang_line=$(find_key_line_number "$lang_file" "$key")
+ printf " %3d. %s (%s:%s)\n" "$counter" "$key" "$(basename "$lang_file")" "$lang_line"
+ counter=$((counter + 1))
+ fi
+ done <<< "$empty_keys"
+ echo ""
+
+ print_color $BLUE "Removing empty keys from $lang_name..." >&2
+ local removed_empty_count=0
+ local failed_empty_removal_count=0
+
+ while IFS= read -r key; do
+ if [[ -n "$key" ]]; then
+ print_color $YELLOW " Removing empty key: $key" >&2
+
+ if remove_json_key "$lang_file" "$key"; then
+ print_color $GREEN " ✓ Removed: $key" >&2
+ removed_empty_count=$((removed_empty_count + 1))
+ else
+ print_color $RED " ✗ Failed to remove: $key" >&2
+ failed_empty_removal_count=$((failed_empty_removal_count + 1))
+ fi
+ fi
+ done <<< "$empty_keys"
+
+ echo ""
+ print_color $GREEN "Empty key removal complete: $removed_empty_count removed, $failed_empty_removal_count failed" >&2
+ echo ""
+ else
+ echo "✅ No empty keys in $lang_name"
+ echo ""
+ fi
+
+ # Remove empty objects (nested objects with no actual values)
+ print_color $BLUE "Cleaning up empty objects in $lang_name..." >&2
+ if remove_empty_objects "$lang_file"; then
+ print_color $GREEN "✓ Successfully removed all empty objects" >&2
+ echo ""
+ else
+ print_color $RED "✗ Failed to clean up empty objects" >&2
+ echo ""
+ fi
+ fi
+
# Clean up
rm -f "$lang_keys_file"
}
@@ -545,7 +737,7 @@ main() {
echo "Target language: $target_language"
fi
if $TRANSLATE_MODE; then
- echo "Translation mode: ENABLED"
+ echo "Translation mode: ENABLED (translated missing keys, removed extra keys, removed empty keys and objects)"
fi
echo "Report generated: $(date '+%Y-%m-%d %H:%M:%S')"
echo ""
@@ -568,7 +760,9 @@ show_usage() {
echo "This script compares JSON language files in '$FOLDER_PATH' against the English reference." >&2
echo "" >&2
echo "Arguments:" >&2
- echo " --translate Enable automatic translation of missing keys using Gemini API" >&2
+ echo " --translate Enable automatic translation of missing keys, removal of extra keys," >&2
+ echo " removal of empty keys (empty strings or null values), and removal of" >&2
+ echo " empty objects (nested objects containing no actual values)" >&2
echo " --list-models List all available Gemini models and exit" >&2
echo " language_code Optional. Compare only the specified language (e.g., 'fr', 'es', 'de')" >&2
echo " If not provided, all language files will be compared" >&2
@@ -581,8 +775,8 @@ show_usage() {
echo " $0 # Compare all languages" >&2
echo " $0 fr # Compare only French (fr.json)" >&2
echo " $0 --list-models # List available Gemini models" >&2
- echo " $0 --translate # Compare all and translate missing keys" >&2
- echo " $0 --translate fr # Translate missing keys for French only" >&2
+ echo " $0 --translate # Compare all, translate missing, remove extra/empty keys and objects" >&2
+ echo " $0 --translate fr # Translate and clean French only" >&2
echo "" >&2
echo "Requirements:" >&2
echo " - jq must be installed" >&2
@@ -595,6 +789,7 @@ show_usage() {
echo " - Comparison report is printed to stdout" >&2
echo " - Progress messages are printed to stderr" >&2
echo " - Results are sorted by descending line numbers for easier editing" >&2
+ echo " - In translate mode, extra keys, empty keys, and empty objects are removed" >&2
}
# Handle command line arguments
diff --git a/Bin/i18n-qml-check.sh b/Bin/dev/i18n-qml-check.sh
similarity index 100%
rename from Bin/i18n-qml-check.sh
rename to Bin/dev/i18n-qml-check.sh
diff --git a/Bin/notifications-test.sh b/Bin/dev/notifications-test.sh
similarity index 100%
rename from Bin/notifications-test.sh
rename to Bin/dev/notifications-test.sh
diff --git a/Bin/qmlfmt.sh b/Bin/dev/qmlfmt.sh
similarity index 100%
rename from Bin/qmlfmt.sh
rename to Bin/dev/qmlfmt.sh
diff --git a/Bin/shaders-compile.sh b/Bin/dev/shaders-compile.sh
similarity index 100%
rename from Bin/shaders-compile.sh
rename to Bin/dev/shaders-compile.sh
diff --git a/Commons/I18n.qml b/Commons/I18n.qml
index 3a013c57..11180047 100644
--- a/Commons/I18n.qml
+++ b/Commons/I18n.qml
@@ -54,6 +54,9 @@ Singleton {
var data = JSON.parse(text())
root.translations = data
Logger.log("I18n", `Loaded translations for "${root.langCode}"`)
+ if (debug) {
+ Logger.log("I18n", `Available root keys: ${Object.keys(data).join(", ")}`)
+ }
root.isLoaded = true
root.translationsLoaded()
@@ -279,9 +282,9 @@ Singleton {
interpolations = {}
if (!isLoaded) {
- // if (debug) {
- // Logger.warn("I18n", "Translations not loaded yet")
- // }
+ if (debug) {
+ Logger.warn("I18n", "Translations not loaded yet")
+ }
return key
}
@@ -291,12 +294,19 @@ Singleton {
// Look-up translation in the active language
var value = translations
var notFound = false
+ if (debug) {
+ Logger.log("I18n", `Looking up key: "${key}"`)
+ }
for (var i = 0; i < keys.length; i++) {
if (value && typeof value === "object" && keys[i] in value) {
value = value[keys[i]]
+ if (debug) {
+ Logger.log("I18n", `Found key part "${keys[i]}"`)
+ }
} else {
if (debug) {
- Logger.warn("I18n", `Translation key "${key}" not found`)
+ Logger.warn("I18n", `Translation key "${key}" not found at part "${keys[i]}"`)
+ Logger.warn("I18n", `Available keys: ${Object.keys(value || {}).join(", ")}`)
}
notFound = true
break
diff --git a/Commons/Settings.qml b/Commons/Settings.qml
index 65749eed..d0308487 100644
--- a/Commons/Settings.qml
+++ b/Commons/Settings.qml
@@ -33,6 +33,7 @@ Singleton {
// Signal emitted when settings are loaded after startupcale changes
signal settingsLoaded
+ signal settingsSaved
// -----------------------------------------------------
// -----------------------------------------------------
@@ -71,11 +72,7 @@ Singleton {
running: false
interval: 1000
onTriggered: {
- settingsFileView.writeAdapter()
- // Write to fallback location if set
- if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) {
- settingsFallbackFileView.writeAdapter()
- }
+ root.saveImmediate()
}
}
@@ -162,10 +159,6 @@ Singleton {
"id": "Tray"
}, {
"id": "NotificationHistory"
- }, {
- "id": "WiFi"
- }, {
- "id": "Bluetooth"
}, {
"id": "Battery"
}, {
@@ -186,10 +179,12 @@ Singleton {
property bool dimDesktop: true
property bool showScreenCorners: false
property bool forceBlackScreenCorners: false
+ property real scaleRatio: 1.0
property real radiusRatio: 1.0
property real screenRadiusRatio: 1.0
property real animationSpeed: 1.0
property bool animationDisabled: false
+ property bool compactLockScreen: false
}
// location
@@ -246,6 +241,43 @@ Singleton {
property JsonObject controlCenter: JsonObject {
// Position: close_to_bar_button, center, top_left, top_right, bottom_left, bottom_right, bottom_center, top_center
property string position: "close_to_bar_button"
+ property JsonObject shortcuts
+ shortcuts: JsonObject {
+ property list left: [{
+ "id": "WiFi"
+ }, {
+ "id": "Bluetooth"
+ }, {
+ "id": "ScreenRecorder"
+ }, {
+ "id": "WallpaperSelector"
+ }]
+ property list right: [{
+ "id": "Notifications"
+ }, {
+ "id": "PowerProfile"
+ }, {
+ "id": "KeepAwake"
+ }, {
+ "id": "NightLight"
+ }]
+ }
+ property list cards: [{
+ "id": "profile-card",
+ "enabled": true
+ }, {
+ "id": "shortcuts-card",
+ "enabled": true
+ }, {
+ "id": "audio-card",
+ "enabled": true
+ }, {
+ "id": "weather-card",
+ "enabled": true
+ }, {
+ "id": "media-sysmon-card",
+ "enabled": true
+ }]
}
// dock
@@ -257,6 +289,7 @@ Singleton {
property list monitors: []
// Desktop entry IDs pinned to the dock (e.g., "org.kde.konsole", "firefox.desktop")
property list pinnedApps: []
+ property bool colorizeIcons: false
}
// network
@@ -283,6 +316,7 @@ Singleton {
property string location: "top_right"
property list monitors: []
property int autoHideMs: 2000
+ property bool alwaysOnTop: false
}
// audio
@@ -301,7 +335,6 @@ Singleton {
property string fontFixed: "DejaVu Sans Mono"
property real fontDefaultScale: 1.0
property real fontFixedScale: 1.0
- property list monitorsScaling: []
property bool idleInhibitorEnabled: false
property bool tooltipsEnabled: true
}
@@ -323,11 +356,18 @@ Singleton {
property JsonObject templates: JsonObject {
property bool gtk: false
property bool qt: false
+ property bool kcolorscheme: false
property bool kitty: false
property bool ghostty: false
property bool foot: false
property bool fuzzel: false
- property bool vesktop: false
+ property bool discord: false
+ property bool discord_vesktop: false
+ property bool discord_webcord: false
+ property bool discord_armcord: false
+ property bool discord_equibop: false
+ property bool discord_lightcord: false
+ property bool discord_dorion: false
property bool pywalfox: false
property bool enableUserTemplates: false
}
@@ -356,6 +396,34 @@ Singleton {
}
}
+ // -----------------------------------------------------
+ // Function to preprocess paths by expanding "~" to user's home directory
+ function preprocessPath(path) {
+ if (typeof path !== "string" || path === "") {
+ return path
+ }
+
+ // Expand "~" to user's home directory
+ if (path.startsWith("~/")) {
+ return Quickshell.env("HOME") + path.substring(1)
+ } else if (path === "~") {
+ return Quickshell.env("HOME")
+ }
+
+ return path
+ }
+
+ // -----------------------------------------------------
+ // Public function to trigger immediate settings saving
+ function saveImmediate() {
+ settingsFileView.writeAdapter()
+ // Write to fallback location if set
+ if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) {
+ settingsFallbackFileView.writeAdapter()
+ }
+ root.settingsSaved() // Emit signal after saving
+ }
+
// -----------------------------------------------------
// Generate default settings at the root of the repo
function generateDefaultSettings() {
diff --git a/Commons/Style.qml b/Commons/Style.qml
index ab09232f..aaad6128 100644
--- a/Commons/Style.qml
+++ b/Commons/Style.qml
@@ -29,27 +29,25 @@ Singleton {
property int fontWeightBold: 700
// Radii
- property int radiusXXS: 4 * Settings.data.general.radiusRatio
- property int radiusXS: 8 * Settings.data.general.radiusRatio
- property int radiusS: 12 * Settings.data.general.radiusRatio
- property int radiusM: 16 * Settings.data.general.radiusRatio
- property int radiusL: 20 * Settings.data.general.radiusRatio
-
- //screen Radii
- property int screenRadius: 20 * Settings.data.general.screenRadiusRatio
+ property int radiusXXS: Math.round(4 * Settings.data.general.radiusRatio)
+ property int radiusXS: Math.round(8 * Settings.data.general.radiusRatio)
+ property int radiusS: Math.round(12 * Settings.data.general.radiusRatio)
+ property int radiusM: Math.round(16 * Settings.data.general.radiusRatio)
+ property int radiusL: Math.round(20 * Settings.data.general.radiusRatio)
+ property int screenRadius: Math.round(20 * Settings.data.general.screenRadiusRatio)
// Border
- property int borderS: 1
- property int borderM: 2
- property int borderL: 3
+ property int borderS: Math.round(1 * uiScaleRatio)
+ property int borderM: Math.round(2 * uiScaleRatio)
+ property int borderL: Math.round(3 * uiScaleRatio)
// Margins (for margins and spacing)
- property int marginXXS: 2
- property int marginXS: 4
- property int marginS: 8
- property int marginM: 12
- property int marginL: 16
- property int marginXL: 24
+ property int marginXXS: Math.round(2 * uiScaleRatio)
+ property int marginXS: Math.round(4 * uiScaleRatio)
+ property int marginS: Math.round(6 * uiScaleRatio)
+ property int marginM: Math.round(9 * uiScaleRatio)
+ property int marginL: Math.round(13 * uiScaleRatio)
+ property int marginXL: Math.round(18 * uiScaleRatio)
// Opacity
property real opacityNone: 0.0
@@ -70,31 +68,39 @@ Singleton {
property int tooltipDelayLong: 1200
property int pillDelay: 500
- // Settings widgets base size
+ // Widgets base size
property real baseWidgetSize: 33
property real sliderWidth: 200
+ property real uiScaleRatio: Settings.data.general.scaleRatio
+
// Bar Dimensions
property real barHeight: {
- if (Settings.data.bar.density === "compact") {
+ switch (Settings.data.bar.density) {
+ case "mini":
+ return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 22 : 20
+ case "compact":
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 27 : 25
- }
- if (Settings.data.bar.density === "default") {
- return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 33 : 31
- }
- if (Settings.data.bar.density === "comfortable") {
+ case "comfortable":
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 39 : 37
+ default:
+
+ case "default":
+ return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 33 : 31
}
}
property real capsuleHeight: {
- if (Settings.data.bar.density === "compact") {
+ switch (Settings.data.bar.density) {
+ case "mini":
+ return barHeight * 1.0
+ case "compact":
return barHeight * 0.85
- }
- if (Settings.data.bar.density === "default") {
- return barHeight * 0.82
- }
- if (Settings.data.bar.density === "comfortable") {
+ case "comfortable":
return barHeight * 0.73
+ default:
+
+ case "default":
+ return barHeight * 0.82
}
}
}
diff --git a/Commons/TablerIcons.qml b/Commons/TablerIcons.qml
index 39743adb..3522e30d 100644
--- a/Commons/TablerIcons.qml
+++ b/Commons/TablerIcons.qml
@@ -101,7 +101,9 @@ Singleton {
"brightness-low": "brightness-down-filled",
"brightness-high": "brightness-up-filled",
"settings-general": "adjustments-horizontal",
- "settings-bar": "capsule-horizontal",
+ "settings-bar": "crop-16-9",
+ "settings-user-interface": "layout-board",
+ "settings-control-center": "adjustments-horizontal",
"settings-dock": "layout-bottombar",
"settings-launcher": "rocket",
"settings-audio": "device-speaker",
diff --git a/Commons/Time.qml b/Commons/Time.qml
index 9c01cc96..16dbfdca 100644
--- a/Commons/Time.qml
+++ b/Commons/Time.qml
@@ -41,7 +41,7 @@ Singleton {
return `${year}${month}${day}-${hours}${minutes}${seconds}`
}
- // Format an easy to read approximate duration ex: 4h32m
+ // Format an easy to read approximate duration ex: 4h 32m
// Used to display the time remaining on the Battery widget, computer uptime, etc..
function formatVagueHumanReadableDuration(totalSeconds) {
if (typeof totalSeconds !== 'number' || totalSeconds < 0) {
@@ -69,7 +69,7 @@ Singleton {
parts.push(`${seconds}s`)
}
- return parts.join('')
+ return parts.join(' ')
}
// Format a date into
diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml
index 3760ff0c..2bcbb04f 100644
--- a/Modules/Background/Background.qml
+++ b/Modules/Background/Background.qml
@@ -44,19 +44,6 @@ Variants {
property real fillMode: WallpaperService.getFillModeUniform()
property vector4d fillColor: Qt.vector4d(Settings.data.wallpaper.fillColor.r, Settings.data.wallpaper.fillColor.g, Settings.data.wallpaper.fillColor.b, 1.0)
- property int monitoredWidth: modelData.width
- property int monitoredHeight: modelData.height
-
- onMonitoredWidthChanged: {
- Logger.log("Background", "Screen width changed to:", monitoredWidth, "for", modelData.name)
- recalculateImageSizes()
- }
-
- onMonitoredHeightChanged: {
- Logger.log("Background", "Screen height changed to:", monitoredHeight, "for", modelData.name)
- recalculateImageSizes()
- }
-
Component.onCompleted: setWallpaperInitial()
Component.onDestruction: {
@@ -87,6 +74,13 @@ Variants {
}
}
+ Connections {
+ target: CompositorService
+ function onDisplayScalesChanged() {
+ setWallpaperInitial()
+ }
+ }
+
color: Color.transparent
screen: modelData
WlrLayershell.layer: WlrLayer.Background
@@ -121,33 +115,22 @@ Variants {
visible: false
cache: false
asynchronous: true
-
+ sourceSize: undefined
onStatusChanged: {
if (status === Image.Error) {
Logger.warn("Current wallpaper failed to load:", source)
} else if (status === Image.Ready && !dimensionsCalculated) {
dimensionsCalculated = true
- calculateSourceSize()
+ const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight)
+ if (optimalSize !== false) {
+ sourceSize = optimalSize
+ }
}
}
-
onSourceChanged: {
dimensionsCalculated = false
sourceSize = undefined
}
-
- function calculateSourceSize() {
- if (implicitWidth > 0 && implicitHeight > 0) {
- const imageAspectRatio = implicitWidth / implicitHeight
- if (modelData.width >= modelData.height) {
- const w = Math.min(modelData.width, implicitWidth)
- sourceSize = Qt.size(w, w / imageAspectRatio)
- } else {
- const h = Math.min(modelData.height, implicitHeight)
- sourceSize = Qt.size(h * imageAspectRatio, h)
- }
- }
- }
}
Image {
@@ -161,33 +144,22 @@ Variants {
visible: false
cache: false
asynchronous: true
-
+ sourceSize: undefined
onStatusChanged: {
if (status === Image.Error) {
Logger.warn("Next wallpaper failed to load:", source)
} else if (status === Image.Ready && !dimensionsCalculated) {
dimensionsCalculated = true
- calculateSourceSize()
+ const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight)
+ if (optimalSize !== false) {
+ sourceSize = optimalSize
+ }
}
}
-
onSourceChanged: {
dimensionsCalculated = false
sourceSize = undefined
}
-
- function calculateSourceSize() {
- if (implicitWidth > 0 && implicitHeight > 0) {
- const imageAspectRatio = implicitWidth / implicitHeight
- if (modelData.width >= modelData.height) {
- const w = Math.min(modelData.width, implicitWidth)
- sourceSize = Qt.size(w, w / imageAspectRatio)
- } else {
- const h = Math.min(modelData.height, implicitHeight)
- sourceSize = Qt.size(h * imageAspectRatio, h)
- }
- }
- }
}
// Dynamic shader loader - only loads the active transition shader
@@ -344,6 +316,31 @@ Variants {
}
}
+ // ------------------------------------------------------
+ function calculateOptimalWallpaperSize(wpWidth, wpHeight) {
+ const compositorScale = CompositorService.getDisplayScale(modelData.name)
+ const screenWidth = modelData.width * compositorScale
+ const screenHeight = modelData.height * compositorScale
+ if (wpWidth <= screenWidth || wpHeight <= screenHeight || wpWidth <= 0 || wpHeight <= 0) {
+ // Do not resize if wallpaper is smaller than one of the screen dimension
+ return
+ }
+
+ const imageAspectRatio = wpWidth / wpHeight
+ var dim = Qt.size(0, 0)
+ if (screenWidth >= screenHeight) {
+ const w = Math.min(screenWidth, wpWidth)
+ dim = Qt.size(w, w / imageAspectRatio)
+ } else {
+ const h = Math.min(screenHeight, wpHeight)
+ dim = Qt.size(h * imageAspectRatio, h)
+ }
+
+ Logger.log("Background", `Wallpaper resized on ${modelData.name} ${screenWidth}x${screenHeight} @ ${compositorScale}x`, "src:", wpWidth, wpHeight, "dst:", dim.width, dim.height)
+ return dim
+ }
+
+ // ------------------------------------------------------
function recalculateImageSizes() {
if (currentWallpaper.status === Image.Ready) {
currentWallpaper.calculateSourceSize()
@@ -353,6 +350,7 @@ Variants {
}
}
+ // ------------------------------------------------------
function setWallpaperInitial() {
// On startup, defer assigning wallpaper until the service cache is ready, retries every tick
if (!WallpaperService || !WallpaperService.isInitialized) {
@@ -363,6 +361,7 @@ Variants {
setWallpaperImmediate(WallpaperService.getWallpaper(modelData.name))
}
+ // ------------------------------------------------------
function setWallpaperImmediate(source) {
transitionAnimation.stop()
transitionProgress = 0.0
@@ -376,6 +375,7 @@ Variants {
})
}
+ // ------------------------------------------------------
function setWallpaperWithTransition(source) {
if (source === currentWallpaper.source) {
return
@@ -409,6 +409,7 @@ Variants {
transitionAnimation.start()
}
+ // ------------------------------------------------------
// Main method that actually trigger the wallpaper change
function changeWallpaper() {
// Get the transitionType from the settings
diff --git a/Modules/Background/ScreenCorners.qml b/Modules/Background/ScreenCorners.qml
index c8302ca6..7ddda724 100644
--- a/Modules/Background/ScreenCorners.qml
+++ b/Modules/Background/ScreenCorners.qml
@@ -16,21 +16,11 @@ Loader {
id: root
required property ShellScreen modelData
- property real scaling: ScalingService.getScreenScale(screen)
screen: modelData
property color cornerColor: Settings.data.general.forceBlackScreenCorners ? Qt.rgba(0, 0, 0, 1) : Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
- property real cornerRadius: Style.screenRadius * scaling
- property real cornerSize: Style.screenRadius * scaling
-
- Connections {
- target: ScalingService
- function onScaleChanged(screenName, scale) {
- if (screenName === screen.name) {
- scaling = scale
- }
- }
- }
+ property real cornerRadius: Style.screenRadius
+ property real cornerSize: Style.screenRadius
color: Color.transparent
@@ -48,10 +38,10 @@ Loader {
margins {
// When bar is floating, corners should be at screen edges (no margins)
// When bar is not floating, respect bar margins as before
- top: !Settings.data.bar.floating && BarService.isVisible && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "top" && Settings.data.bar.backgroundOpacity > 0 ? Math.round(Style.barHeight * scaling) : 0
- bottom: !Settings.data.bar.floating && BarService.isVisible && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "bottom" && Settings.data.bar.backgroundOpacity > 0 ? Math.round(Style.barHeight * scaling) : 0
- left: !Settings.data.bar.floating && BarService.isVisible && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "left" && Settings.data.bar.backgroundOpacity > 0 ? Math.round(Style.barHeight * scaling) : 0
- right: !Settings.data.bar.floating && BarService.isVisible && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "right" && Settings.data.bar.backgroundOpacity > 0 ? Math.round(Style.barHeight * scaling) : 0
+ top: !Settings.data.bar.floating && BarService.isVisible && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "top" && Settings.data.bar.backgroundOpacity > 0 ? Style.barHeight : 0
+ bottom: !Settings.data.bar.floating && BarService.isVisible && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "bottom" && Settings.data.bar.backgroundOpacity > 0 ? Style.barHeight : 0
+ left: !Settings.data.bar.floating && BarService.isVisible && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "left" && Settings.data.bar.backgroundOpacity > 0 ? Style.barHeight : 0
+ right: !Settings.data.bar.floating && BarService.isVisible && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "right" && Settings.data.bar.backgroundOpacity > 0 ? Style.barHeight : 0
}
mask: Region {}
diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml
index dd6d950c..fa61bd95 100644
--- a/Modules/Bar/Bar.qml
+++ b/Modules/Bar/Bar.qml
@@ -17,16 +17,6 @@ Variants {
id: root
required property ShellScreen modelData
- property real scaling: ScalingService.getScreenScale(modelData)
-
- Connections {
- target: ScalingService
- function onScaleChanged(screenName, scale) {
- if ((modelData !== null) && (screenName === modelData.name)) {
- scaling = scale
- }
- }
- }
active: BarService.isVisible && modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false
@@ -35,8 +25,8 @@ Variants {
WlrLayershell.namespace: "noctalia-bar"
- implicitHeight: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? screen.height : Math.round(Style.barHeight * scaling)
- implicitWidth: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? Math.round(Style.barHeight * scaling) : screen.width
+ implicitHeight: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? screen.height : Style.barHeight
+ implicitWidth: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? Style.barHeight : screen.width
color: Color.transparent
anchors {
@@ -49,10 +39,10 @@ Variants {
// Floating bar margins - only apply when floating is enabled
// Also don't apply margin on the opposite side ot the bar orientation, ex: if bar is floating on top, margin is only applied on top, not bottom.
margins {
- top: Settings.data.bar.floating && Settings.data.bar.position !== "bottom" ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
- bottom: Settings.data.bar.floating && Settings.data.bar.position !== "top" ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
- left: Settings.data.bar.floating && Settings.data.bar.position !== "right" ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
- right: Settings.data.bar.floating && Settings.data.bar.position !== "left" ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
+ top: Settings.data.bar.floating && Settings.data.bar.position !== "bottom" ? Settings.data.bar.marginVertical * Style.marginXL : 0
+ bottom: Settings.data.bar.floating && Settings.data.bar.position !== "top" ? Settings.data.bar.marginVertical * Style.marginXL : 0
+ left: Settings.data.bar.floating && Settings.data.bar.position !== "right" ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
+ right: Settings.data.bar.floating && Settings.data.bar.position !== "left" ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
}
Component.onCompleted: {
@@ -105,8 +95,8 @@ Variants {
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
- anchors.topMargin: Style.marginM * root.scaling
- spacing: Style.marginS * root.scaling
+ anchors.topMargin: Style.marginM
+ spacing: Style.marginS
Repeater {
model: Settings.data.bar.widgets.left
@@ -114,7 +104,6 @@ Variants {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
- "scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "left",
"sectionWidgetIndex": index,
@@ -129,7 +118,7 @@ Variants {
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
- spacing: Style.marginS * root.scaling
+ spacing: Style.marginS
Repeater {
model: Settings.data.bar.widgets.center
@@ -137,7 +126,6 @@ Variants {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
- "scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "center",
"sectionWidgetIndex": index,
@@ -152,8 +140,8 @@ Variants {
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
- anchors.bottomMargin: Style.marginM * root.scaling
- spacing: Style.marginS * root.scaling
+ anchors.bottomMargin: Style.marginM
+ spacing: Style.marginS
Repeater {
model: Settings.data.bar.widgets.right
@@ -161,7 +149,6 @@ Variants {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
- "scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "right",
"sectionWidgetIndex": index,
@@ -185,9 +172,9 @@ Variants {
id: leftSection
objectName: "leftSection"
anchors.left: parent.left
- anchors.leftMargin: Style.marginS * root.scaling
+ anchors.leftMargin: Style.marginS
anchors.verticalCenter: parent.verticalCenter
- spacing: Style.marginS * root.scaling
+ spacing: Style.marginS
Repeater {
model: Settings.data.bar.widgets.left
@@ -195,7 +182,6 @@ Variants {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
- "scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "left",
"sectionWidgetIndex": index,
@@ -212,7 +198,7 @@ Variants {
objectName: "centerSection"
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
- spacing: Style.marginS * root.scaling
+ spacing: Style.marginS
Repeater {
model: Settings.data.bar.widgets.center
@@ -220,7 +206,6 @@ Variants {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
- "scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "center",
"sectionWidgetIndex": index,
@@ -236,9 +221,9 @@ Variants {
id: rightSection
objectName: "rightSection"
anchors.right: parent.right
- anchors.rightMargin: Style.marginS * root.scaling
+ anchors.rightMargin: Style.marginS
anchors.verticalCenter: parent.verticalCenter
- spacing: Style.marginS * root.scaling
+ spacing: Style.marginS
Repeater {
model: Settings.data.bar.widgets.right
@@ -246,7 +231,6 @@ Variants {
widgetId: (modelData.id !== undefined ? modelData.id : "")
widgetProps: {
"screen": root.modelData || null,
- "scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"section": "right",
"sectionWidgetIndex": index,
diff --git a/Modules/Bar/Bluetooth/BluetoothDevicesList.qml b/Modules/Bar/Bluetooth/BluetoothDevicesList.qml
index f40b13e1..8e144c31 100644
--- a/Modules/Bar/Bluetooth/BluetoothDevicesList.qml
+++ b/Modules/Bar/Bluetooth/BluetoothDevicesList.qml
@@ -18,11 +18,11 @@ ColumnLayout {
}
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NText {
text: root.label
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
color: Color.mSecondary
font.weight: Style.fontWeightMedium
Layout.fillWidth: true
@@ -51,35 +51,35 @@ ColumnLayout {
}
Layout.fillWidth: true
- Layout.preferredHeight: deviceLayout.implicitHeight + (Style.marginM * scaling * 2)
- radius: Style.radiusM * scaling
+ Layout.preferredHeight: deviceLayout.implicitHeight + (Style.marginM * 2)
+ radius: Style.radiusM
color: Color.mSurface
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
border.color: getContentColor(Color.mOutline)
RowLayout {
id: deviceLayout
anchors.fill: parent
- anchors.margins: Style.marginM * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginM
+ spacing: Style.marginM
Layout.alignment: Qt.AlignVCenter
// One device BT icon
NIcon {
icon: BluetoothService.getDeviceIcon(modelData)
- pointSize: Style.fontSizeXXL * scaling
+ pointSize: Style.fontSizeXXL
color: getContentColor(Color.mOnSurface)
Layout.alignment: Qt.AlignVCenter
}
ColumnLayout {
Layout.fillWidth: true
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
// Device name
NText {
text: modelData.name || modelData.deviceName
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
elide: Text.ElideRight
color: getContentColor(Color.mOnSurface)
@@ -90,7 +90,7 @@ ColumnLayout {
NText {
text: BluetoothService.getStatusString(modelData)
visible: text !== ""
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
color: getContentColor(Color.mOnSurfaceVariant)
}
@@ -98,26 +98,26 @@ ColumnLayout {
RowLayout {
visible: modelData.signalStrength !== undefined
Layout.fillWidth: true
- spacing: Style.marginXS * scaling
+ spacing: Style.marginXS
// Device signal strength - "Unknown" when not connected
NText {
text: BluetoothService.getSignalStrength(modelData)
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
color: getContentColor(Color.mOnSurfaceVariant)
}
NIcon {
visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
text: BluetoothService.getSignalIcon(modelData)
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
color: getContentColor(Color.mOnSurface)
}
NText {
visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
text: (modelData.signalStrength !== undefined && modelData.signalStrength > 0) ? modelData.signalStrength + "%" : ""
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
color: getContentColor(Color.mOnSurface)
}
}
@@ -126,7 +126,7 @@ ColumnLayout {
NText {
visible: modelData.batteryAvailable
text: BluetoothService.getBattery(modelData)
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
color: getContentColor(Color.mOnSurfaceVariant)
}
}
@@ -142,7 +142,7 @@ ColumnLayout {
visible: (modelData.state !== BluetoothDeviceState.Connecting)
enabled: (canConnect || canDisconnect) && !isBusy
outlined: !button.hovered
- fontSize: Style.fontSizeXS * scaling
+ fontSize: Style.fontSizeXS
fontWeight: Style.fontWeightMedium
backgroundColor: {
if (device.canDisconnect && !isBusy) {
diff --git a/Modules/Bar/Bluetooth/BluetoothPanel.qml b/Modules/Bar/Bluetooth/BluetoothPanel.qml
index c11d38eb..7e08efef 100644
--- a/Modules/Bar/Bluetooth/BluetoothPanel.qml
+++ b/Modules/Bar/Bluetooth/BluetoothPanel.qml
@@ -11,8 +11,8 @@ import qs.Widgets
NPanel {
id: root
- preferredWidth: 380
- preferredHeight: 500
+ preferredWidth: 380 * Style.uiScaleRatio
+ preferredHeight: 500 * Style.uiScaleRatio
panelKeyboardFocus: true
panelContent: Rectangle {
@@ -20,23 +20,23 @@ NPanel {
ColumnLayout {
anchors.fill: parent
- anchors.margins: Style.marginL * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginL
+ spacing: Style.marginM
// HEADER
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NIcon {
icon: "bluetooth"
- pointSize: Style.fontSizeXXL * scaling
+ pointSize: Style.fontSizeXXL
color: Color.mPrimary
}
NText {
text: I18n.tr("bluetooth.panel.title")
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.fillWidth: true
@@ -46,7 +46,7 @@ NPanel {
id: bluetoothSwitch
checked: BluetoothService.enabled
onToggled: checked => BluetoothService.setBluetoothEnabled(checked)
- baseSize: Style.baseWidgetSize * 0.65 * scaling
+ baseSize: Style.baseWidgetSize * 0.65
}
NIconButton {
@@ -84,25 +84,25 @@ NPanel {
// Center the content within this rectangle
ColumnLayout {
anchors.centerIn: parent
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NIcon {
icon: "bluetooth-off"
- pointSize: 64 * scaling
+ pointSize: 64
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: I18n.tr("bluetooth.panel.disabled")
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: I18n.tr("bluetooth.panel.enable-message")
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
@@ -120,7 +120,7 @@ NPanel {
ColumnLayout {
width: parent.width
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Connected devices
BluetoothDevicesList {
@@ -168,7 +168,7 @@ NPanel {
// Fallback - No devices, scanning
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
visible: {
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices) {
return false
@@ -182,11 +182,11 @@ NPanel {
RowLayout {
Layout.alignment: Qt.AlignHCenter
- spacing: Style.marginXS * scaling
+ spacing: Style.marginXS
NIcon {
icon: "refresh"
- pointSize: Style.fontSizeXXL * 1.5 * scaling
+ pointSize: Style.fontSizeXXL * 1.5
color: Color.mPrimary
RotationAnimation on rotation {
@@ -200,14 +200,14 @@ NPanel {
NText {
text: I18n.tr("bluetooth.panel.scanning")
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
color: Color.mOnSurface
}
}
NText {
text: I18n.tr("bluetooth.panel.pairing-mode")
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml
index c0c0f5ee..68b27ab3 100644
--- a/Modules/Bar/Calendar/CalendarPanel.qml
+++ b/Modules/Bar/Calendar/CalendarPanel.qml
@@ -10,22 +10,40 @@ import qs.Widgets
NPanel {
id: root
- preferredWidth: Settings.data.location.showWeekNumberInCalendar ? 340 : 320
- preferredHeight: 380
+ readonly property var now: Time.date
+
+ preferredWidth: (Settings.data.location.showWeekNumberInCalendar ? 400 : 380) * Style.uiScaleRatio
+ preferredHeight: 520 * Style.uiScaleRatio
+ panelKeyboardFocus: true
panelContent: ColumnLayout {
id: content
anchors.fill: parent
- anchors.margins: Style.marginL * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginL
+ spacing: Style.marginM
readonly property int firstDayOfWeek: Qt.locale().firstDayOfWeek
property bool isCurrentMonth: checkIsCurrentMonth()
+ readonly property bool weatherReady: (LocationService.data.weather !== null)
function checkIsCurrentMonth() {
return (Time.date.getMonth() === grid.month) && (Time.date.getFullYear() === grid.year)
}
+ Shortcut {
+ sequence: "Escape"
+ onActivated: {
+ if (timerActive) {
+ cancelTimer()
+ } else {
+ cancelTimer()
+ root.close()
+ }
+ }
+ context: Qt.WidgetShortcut
+ enabled: root.opened
+ }
+
Connections {
target: Time
function onDateChanged() {
@@ -33,143 +51,290 @@ NPanel {
}
}
- // Current day
+ // Combined blue banner with date/time and weather summary
Rectangle {
Layout.fillWidth: true
- Layout.preferredHeight: 80 * scaling
- radius: Style.radiusL * scaling
+ Layout.preferredHeight: blueColumn.implicitHeight + Style.marginM * 2
+ radius: Style.radiusL
color: Color.mPrimary
- RowLayout {
- anchors.fill: parent
- anchors.margins: Style.marginL * scaling
- anchors.topMargin: 12 * scaling
- spacing: Style.marginM * scaling
+ ColumnLayout {
+ id: blueColumn
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ anchors.topMargin: Style.marginM
+ anchors.leftMargin: Style.marginM
+ anchors.bottomMargin: Style.marginM
+ anchors.rightMargin: clockItem.width + (Style.marginM * 2)
+ spacing: 0
- // Month, Year and Day
+ // Combined layout for weather icon, date, and weather text
RowLayout {
- Layout.alignment: Qt.AlignVCenter
- Layout.fillHeight: true
- spacing: Style.marginM * scaling
+ Layout.fillWidth: true
+ height: 60 * Style.uiScaleRatio
+ clip: true
+ spacing: Style.marginS
- // Big day of the month
+ // Weather icon and temperature
+ ColumnLayout {
+ Layout.alignment: Qt.AlignVCenter
+ spacing: Style.marginXXS
+
+ NIcon {
+ Layout.alignment: Qt.AlignHCenter
+ icon: weatherReady ? LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode) : "cloud"
+ pointSize: Style.fontSizeXXL
+ color: Color.mOnPrimary
+ }
+
+ NText {
+ Layout.alignment: Qt.AlignHCenter
+ text: {
+ if (!weatherReady)
+ return ""
+ var temp = LocationService.data.weather.current_weather.temperature
+ var suffix = "C"
+ if (Settings.data.location.useFahrenheit) {
+ temp = LocationService.celsiusToFahrenheit(temp)
+ suffix = "F"
+ }
+ temp = Math.round(temp)
+ return `${temp}°${suffix}`
+ }
+ pointSize: Style.fontSizeM
+ font.weight: Style.fontWeightBold
+ color: Color.mOnPrimary
+ }
+ }
+
+ // 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: Time.date.getDate()
- pointSize: Style.fontSizeXXXL * 1.5 * scaling
+ pointSize: Style.fontSizeXXXL * 1.5
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
- visible: isCurrentMonth
- Layout.preferredWidth: visible ? implicitWidth : 0
- Layout.fillHeight: true
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: Style.animationFast
+ }
+ }
+ Behavior on Layout.preferredWidth {
+ NumberAnimation {
+ duration: Style.animationFast
+ easing.type: Easing.InOutQuad
+ }
+ }
}
- // Month and Year
+ // Month, year, location
ColumnLayout {
- spacing: 0
+ Layout.preferredWidth: 170 * Style.uiScaleRatio
+ Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
+ Layout.bottomMargin: Style.marginXXS
+ Layout.topMargin: -Style.marginXXS
+ spacing: -Style.marginXS
- NText {
- text: Qt.locale().monthName(grid.month, Locale.LongFormat).toUpperCase()
- pointSize: Style.fontSizeL * scaling
- font.weight: Style.fontWeightBold
- color: Color.mOnPrimary
- }
+ RowLayout {
+ spacing: 0
- NText {
- text: grid.year
- pointSize: Style.fontSizeM * scaling
- color: Qt.alpha(Color.mOnPrimary, 0.8)
- }
- }
- }
+ NText {
+ text: Qt.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
+ }
- Item {
- Layout.fillWidth: true
- }
-
- // Digital clock with circular progress
- Item {
- width: Style.fontSizeXXXL * 2 * scaling
- height: Style.fontSizeXXXL * 2 * scaling
-
- // Seconds circular progress
- Canvas {
- id: secondsProgress
- anchors.fill: parent
-
- property real progress: Time.date.getSeconds() / 60
- onProgressChanged: requestPaint()
-
- Connections {
- target: Time
- function onDateChanged() {
- secondsProgress.progress = Time.date.getSeconds() / 60
+ NText {
+ text: ` ${grid.year}`
+ pointSize: Style.fontSizeM
+ font.weight: Style.fontWeightBold
+ color: Qt.alpha(Color.mOnPrimary, 0.7)
+ Layout.alignment: Qt.AlignBaseline
}
}
- onPaint: {
- var ctx = getContext("2d")
- var centerX = width / 2
- var centerY = height / 2
- var radius = Math.min(width, height) / 2 - 4 * scaling
+ RowLayout {
+ spacing: 0
- ctx.reset()
+ NText {
+ text: {
+ if (!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
+ }
- // Background circle
- ctx.beginPath()
- ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI)
- ctx.lineWidth = 3 * scaling
- ctx.strokeStyle = Qt.alpha(Color.mOnPrimary, 0.15)
- ctx.stroke()
-
- // Progress arc
- ctx.beginPath()
- ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI)
- ctx.lineWidth = 3 * scaling
- ctx.strokeStyle = Color.mOnPrimary
- ctx.lineCap = "round"
- ctx.stroke()
+ NText {
+ text: weatherReady ? ` (${LocationService.data.weather.timezone_abbreviation})` : ""
+ pointSize: Style.fontSizeXS
+ font.weight: Style.fontWeightMedium
+ color: Qt.alpha(Color.mOnPrimary, 0.7)
+ }
}
}
- // Digital clock
- ColumnLayout {
- anchors.centerIn: parent
- spacing: -3 * scaling
+ // Spacer to push content left
+ Item {
+ Layout.fillWidth: true
+ }
+ }
+ }
- NText {
- text: {
- var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "hh AP") : Qt.locale().toString(new Date(), "HH")
- return t.split(" ")[0]
- }
- pointSize: Style.fontSizeS * scaling
- font.weight: Style.fontWeightBold
- color: Color.mOnPrimary
- family: Settings.data.ui.fontFixed
- Layout.alignment: Qt.AlignHCenter
+ // Digital clock with circular progress
+ Item {
+ id: clockItem
+ Layout.alignment: Qt.AlignVCenter
+ anchors.right: parent.right
+ anchors.rightMargin: Style.marginM
+ anchors.verticalCenter: parent.verticalCenter
+ height: Math.round((Style.fontSizeXXXL * 1.9) / 2 * Style.uiScaleRatio) * 2
+ width: clockItem.height
+
+ // Seconds circular progress
+ Canvas {
+ id: secondsProgress
+ anchors.fill: parent
+ property real progress: now.getSeconds() / 60
+ onProgressChanged: requestPaint()
+ Connections {
+ target: Time
+ function onDateChanged() {
+ const total = now.getSeconds() * 1000 + now.getMilliseconds()
+ secondsProgress.progress = total / 60000
+ }
+ }
+ onPaint: {
+ var ctx = getContext("2d")
+ var centerX = width / 2
+ var centerY = height / 2
+ var radius = Math.min(width, height) / 2 - 3
+ ctx.reset()
+
+ // Background circle
+ ctx.beginPath()
+ ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI)
+ ctx.lineWidth = 2.5
+ ctx.strokeStyle = Qt.alpha(Color.mOnPrimary, 0.15)
+ ctx.stroke()
+
+ // Progress arc
+ ctx.beginPath()
+ ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI)
+ ctx.lineWidth = 2.5
+ ctx.strokeStyle = Color.mOnPrimary
+ ctx.lineCap = "round"
+ ctx.stroke()
+ }
+ }
+
+ // Digital clock
+ ColumnLayout {
+ anchors.centerIn: parent
+ spacing: -Style.marginXXS
+
+ NText {
+ text: {
+ var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(now, "hh AP") : Qt.locale().toString(now, "HH")
+ return t.split(" ")[0]
}
- NText {
- text: Qt.formatTime(Time.date, "mm")
- pointSize: Style.fontSizeS * scaling
- font.weight: Style.fontWeightBold
- color: Color.mOnPrimary
- family: Settings.data.ui.fontFixed
- Layout.alignment: Qt.AlignHCenter
- }
+ pointSize: Style.fontSizeXS
+ font.weight: Style.fontWeightBold
+ color: Color.mOnPrimary
+ family: Settings.data.ui.fontFixed
+ Layout.alignment: Qt.AlignHCenter
+ }
+
+ NText {
+ text: Qt.formatTime(now, "mm")
+ pointSize: Style.fontSizeXXS
+ font.weight: Style.fontWeightBold
+ color: Color.mOnPrimary
+ family: Settings.data.ui.fontFixed
+ Layout.alignment: Qt.AlignHCenter
}
}
}
}
- // Navigation and divider
+ // ... (rest of the file is unchanged) ...
+ RowLayout {
+ visible: weatherReady
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignHCenter
+ spacing: Style.marginL
+ Repeater {
+ model: weatherReady ? Math.min(6, LocationService.data.weather.daily.time.length) : 0
+ delegate: ColumnLayout {
+ Layout.preferredWidth: 0
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignHCenter
+ spacing: Style.marginS
+ NText {
+ text: {
+ var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"))
+ return Qt.locale().toString(weatherDate, "ddd")
+ }
+ color: Color.mOnSurfaceVariant
+ pointSize: Style.fontSizeM
+ font.weight: Style.fontWeightMedium
+ Layout.alignment: Qt.AlignHCenter
+ }
+ NIcon {
+ Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
+ icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index])
+ pointSize: Style.fontSizeXXL * 1.5
+ color: Color.mPrimary
+ }
+ NText {
+ Layout.alignment: Qt.AlignHCenter
+ text: {
+ var max = LocationService.data.weather.daily.temperature_2m_max[index]
+ var min = LocationService.data.weather.daily.temperature_2m_min[index]
+ if (Settings.data.location.useFahrenheit) {
+ max = LocationService.celsiusToFahrenheit(max)
+ min = LocationService.celsiusToFahrenheit(min)
+ }
+ max = Math.round(max)
+ min = Math.round(min)
+ return `${max}°/${min}°`
+ }
+ pointSize: Style.fontSizeXS
+ color: Color.mOnSurfaceVariant
+ font.weight: Style.fontWeightMedium
+ }
+ }
+ }
+ }
+ RowLayout {
+ visible: !weatherReady
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignHCenter
+ NBusyIndicator {}
+ }
+ Item {}
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginS * scaling
-
+ spacing: Style.marginS
NDivider {
Layout.fillWidth: true
}
-
NIconButton {
icon: "chevron-left"
onClicked: {
@@ -179,7 +344,6 @@ NPanel {
content.isCurrentMonth = content.checkIsCurrentMonth()
}
}
-
NIconButton {
icon: "calendar"
onClicked: {
@@ -188,7 +352,6 @@ NPanel {
content.isCurrentMonth = true
}
}
-
NIconButton {
icon: "chevron-right"
onClicked: {
@@ -199,31 +362,24 @@ NPanel {
}
}
}
-
- // Names of days of the week
RowLayout {
Layout.fillWidth: true
spacing: 0
-
Item {
visible: Settings.data.location.showWeekNumberInCalendar
- Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0
+ 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.baseWidgetSize * 0.6 * scaling
-
+ Layout.preferredHeight: Style.baseWidgetSize * 0.6
NText {
anchors.centerIn: parent
text: {
@@ -232,7 +388,7 @@ NPanel {
return dayNames[dayIndex]
}
color: Color.mPrimary
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
font.weight: Style.fontWeightBold
horizontalAlignment: Text.AlignHCenter
}
@@ -240,31 +396,24 @@ NPanel {
}
}
}
-
- // Grid with weeks and days
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 0
-
- // Column of week numbers
ColumnLayout {
visible: Settings.data.location.showWeekNumberInCalendar
- Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0
+ Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
Layout.fillHeight: true
spacing: 0
-
Repeater {
model: 6
-
Item {
Layout.fillWidth: true
Layout.fillHeight: true
-
NText {
anchors.centerIn: parent
color: Color.mOutline
- pointSize: Style.fontSizeXXS * scaling
+ pointSize: Style.fontSizeXXS
font.weight: Style.fontWeightMedium
text: {
let firstOfMonth = new Date(grid.year, grid.month, 1)
@@ -292,27 +441,21 @@ NPanel {
}
}
}
-
- // Days Grid
MonthGrid {
id: grid
-
Layout.fillWidth: true
Layout.fillHeight: true
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
month: Time.date.getMonth()
year: Time.date.getFullYear()
locale: Qt.locale()
-
delegate: Item {
Rectangle {
- width: Style.baseWidgetSize * 0.9 * scaling
- height: Style.baseWidgetSize * 0.9 * scaling
+ width: Style.baseWidgetSize * 0.9
+ height: Style.baseWidgetSize * 0.9
anchors.centerIn: parent
- radius: Style.radiusM * scaling
-
+ radius: Style.radiusM
color: model.today ? Color.mSecondary : Color.transparent
-
NText {
anchors.centerIn: parent
text: model.day
@@ -324,10 +467,9 @@ NPanel {
return Color.mOnSurfaceVariant
}
opacity: model.month === grid.month ? 1.0 : 0.4
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
font.weight: model.today ? Style.fontWeightBold : Style.fontWeightMedium
}
-
Behavior on color {
ColorAnimation {
duration: Style.animationFast
diff --git a/Modules/Bar/Extras/BarPill.qml b/Modules/Bar/Extras/BarPill.qml
index ff4ae3bb..ba1601d3 100644
--- a/Modules/Bar/Extras/BarPill.qml
+++ b/Modules/Bar/Extras/BarPill.qml
@@ -12,13 +12,13 @@ Item {
property string text: ""
property string suffix: ""
property string tooltipText: ""
+ property string density: ""
property bool autoHide: false
property bool forceOpen: false
property bool forceClose: false
property bool disableOpen: false
property bool rightOpen: false
property bool hovered: false
- property bool compact: false
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"
@@ -54,7 +54,7 @@ Item {
disableOpen: root.disableOpen
rightOpen: root.rightOpen
hovered: root.hovered
- compact: root.compact
+ density: root.density
onShown: root.shown()
onHidden: root.hidden()
onEntered: root.entered()
@@ -79,7 +79,7 @@ Item {
disableOpen: root.disableOpen
rightOpen: root.rightOpen
hovered: root.hovered
- compact: root.compact
+ density: root.density
onShown: root.shown()
onHidden: root.hidden()
onEntered: root.entered()
diff --git a/Modules/Bar/Extras/BarPillHorizontal.qml b/Modules/Bar/Extras/BarPillHorizontal.qml
index a7da0d1d..0c2b5e90 100644
--- a/Modules/Bar/Extras/BarPillHorizontal.qml
+++ b/Modules/Bar/Extras/BarPillHorizontal.qml
@@ -14,13 +14,13 @@ Item {
property string text: ""
property string suffix: ""
property string tooltipText: ""
+ property string density: ""
property bool autoHide: false
property bool forceOpen: false
property bool forceClose: false
property bool disableOpen: false
property bool rightOpen: false
property bool hovered: false
- property bool compact: false
// Effective shown state (true if hovered/animated open or forced)
readonly property bool revealed: !forceClose && (forceOpen || showPill)
@@ -38,13 +38,28 @@ Item {
property bool showPill: false
property bool shouldAnimateHide: false
- readonly property int pillHeight: Math.round(Style.capsuleHeight * scaling)
- readonly property int pillPaddingHorizontal: Math.round(Style.capsuleHeight * 0.2 * scaling)
- readonly property int pillOverlap: Math.round(Style.capsuleHeight * 0.5 * scaling)
- readonly property int pillMaxWidth: Math.max(1, textItem.implicitWidth + pillPaddingHorizontal * 2 + pillOverlap)
+ readonly property int pillHeight: Style.capsuleHeight
+ readonly property int pillPaddingHorizontal: Math.round(Style.capsuleHeight * 0.2)
+ readonly property int pillOverlap: Math.round(Style.capsuleHeight * 0.5)
+ readonly property int pillMaxWidth: Math.max(1, Math.round(textItem.implicitWidth + pillPaddingHorizontal * 2 + pillOverlap))
- readonly property real iconSize: Math.max(1, compact ? pillHeight * 0.65 : pillHeight * 0.48)
- readonly property real textSize: Math.max(1, compact ? pillHeight * 0.45 : pillHeight * 0.33)
+ readonly property real iconSize: {
+ switch (root.density) {
+ case "compact":
+ return Math.max(1, Math.round(pillHeight * 0.65))
+ default:
+ return Math.max(1, Math.round(pillHeight * 0.48))
+ }
+ }
+
+ readonly property real textSize: {
+ switch (root.density) {
+ case "compact":
+ return Math.max(1, Math.round(pillHeight * 0.45))
+ default:
+ return Math.max(1, Math.round(pillHeight * 0.33))
+ }
+ }
width: pillHeight + Math.max(0, pill.width - pillOverlap)
height: pillHeight
@@ -68,10 +83,12 @@ Item {
opacity: revealed ? Style.opacityFull : Style.opacityNone
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
- topLeftRadius: rightOpen ? 0 : pillHeight * 0.5
- bottomLeftRadius: rightOpen ? 0 : pillHeight * 0.5
- topRightRadius: rightOpen ? pillHeight * 0.5 : 0
- bottomRightRadius: rightOpen ? pillHeight * 0.5 : 0
+ readonly property int halfPillHeight: Math.round(pillHeight * 0.5)
+
+ topLeftRadius: rightOpen ? 0 : halfPillHeight
+ bottomLeftRadius: rightOpen ? 0 : halfPillHeight
+ topRightRadius: rightOpen ? halfPillHeight : 0
+ bottomRightRadius: rightOpen ? halfPillHeight : 0
anchors.verticalCenter: parent.verticalCenter
NText {
@@ -80,16 +97,17 @@ Item {
x: {
// Better text horizontal centering
var centerX = (parent.width - width) / 2
- var offset = rightOpen ? Style.marginXS * scaling : -Style.marginXS * scaling
+ var offset = rightOpen ? Style.marginXS : -Style.marginXS
if (forceOpen) {
// If its force open, the icon disc background is the same color as the bg pill move text slightly
- offset += rightOpen ? -Style.marginXXS * scaling : Style.marginXXS * scaling
+ offset += rightOpen ? -Style.marginXXS : Style.marginXXS
}
return centerX + offset
}
text: root.text + root.suffix
family: Settings.data.ui.fontFixed
pointSize: textSize
+ applyUiScale: false
font.weight: Style.fontWeightBold
color: forceOpen ? Color.mOnSurface : Color.mPrimary
visible: revealed
@@ -131,6 +149,7 @@ Item {
NIcon {
icon: root.icon
pointSize: iconSize
+ applyUiScale: false
color: hovered ? Color.mOnTertiary : Color.mOnSurface
// Center horizontally
x: (iconCircle.width - width) / 2
diff --git a/Modules/Bar/Extras/BarPillVertical.qml b/Modules/Bar/Extras/BarPillVertical.qml
index f11b06e6..c8b53997 100644
--- a/Modules/Bar/Extras/BarPillVertical.qml
+++ b/Modules/Bar/Extras/BarPillVertical.qml
@@ -12,13 +12,13 @@ Item {
property string text: ""
property string suffix: ""
property string tooltipText: ""
+ property string density: ""
property bool autoHide: false
property bool forceOpen: false
property bool forceClose: false
property bool disableOpen: false
property bool rightOpen: false
property bool hovered: false
- property bool compact: false
// Bar position detection for pill direction
readonly property string barPosition: Settings.data.bar.position
@@ -45,15 +45,30 @@ Item {
property bool shouldAnimateHide: false
// Sizing logic for vertical bars
- readonly property int buttonSize: Math.round(Style.capsuleHeight * scaling)
+ readonly property int buttonSize: Style.capsuleHeight
readonly property int pillHeight: buttonSize
- readonly property int pillPaddingVertical: 3 * 2 * scaling // Very precise adjustment don't replace by Style.margin
- readonly property int pillOverlap: buttonSize * 0.5
+ readonly property int pillPaddingVertical: 3 * 2 // Very precise adjustment don't replace by Style.margin
+ readonly property int pillOverlap: Math.round(buttonSize * 0.5)
readonly property int maxPillWidth: buttonSize
- readonly property int maxPillHeight: Math.max(1, textItem.implicitHeight + pillPaddingVertical * 4)
+ readonly property int maxPillHeight: Math.max(1, Math.round(textItem.implicitHeight + pillPaddingVertical * 4))
- readonly property real iconSize: Math.max(1, compact ? pillHeight * 0.65 : pillHeight * 0.48)
- readonly property real textSize: Math.max(1, compact ? pillHeight * 0.38 : pillHeight * 0.33)
+ readonly property real iconSize: {
+ switch (root.density) {
+ case "compact":
+ return Math.max(1, Math.round(pillHeight * 0.65))
+ default:
+ return Math.max(1, Math.round(pillHeight * 0.48))
+ }
+ }
+
+ readonly property real textSize: {
+ switch (root.density) {
+ case "compact":
+ return Math.max(1, Math.round(pillHeight * 0.38))
+ default:
+ return Math.max(1, Math.round(pillHeight * 0.33))
+ }
+ }
// For vertical bars: width is just icon size, height includes pill space
width: buttonSize
@@ -79,11 +94,13 @@ Item {
opacity: revealed ? Style.opacityFull : Style.opacityNone
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
+ readonly property int halfButtonSize: Math.round(buttonSize * 0.5)
+
// Radius logic for vertical expansion - rounded on the side that connects to icon
- topLeftRadius: openUpward ? buttonSize * 0.5 : 0
- bottomLeftRadius: openDownward ? buttonSize * 0.5 : 0
- topRightRadius: openUpward ? buttonSize * 0.5 : 0
- bottomRightRadius: openDownward ? buttonSize * 0.5 : 0
+ topLeftRadius: openUpward ? halfButtonSize : 0
+ bottomLeftRadius: openDownward ? halfButtonSize : 0
+ topRightRadius: openUpward ? halfButtonSize : 0
+ bottomRightRadius: openDownward ? halfButtonSize : 0
anchors.horizontalCenter: parent.horizontalCenter
@@ -92,16 +109,17 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: {
- var offset = openDownward ? pillPaddingVertical * 0.75 : -pillPaddingVertical * 0.75
+ var offset = openDownward ? Math.round(pillPaddingVertical * 0.75) : -Math.round(pillPaddingVertical * 0.75)
if (forceOpen) {
// If its force open, the icon disc background is the same color as the bg pill move text slightly
- offset += rightOpen ? -Style.marginXXS * scaling : Style.marginXXS * scaling
+ offset += rightOpen ? -Style.marginXXS : Style.marginXXS
}
return offset
}
text: root.text + root.suffix
family: Settings.data.ui.fontFixed
pointSize: textSize
+ applyUiScale: false
font.weight: Style.fontWeightMedium
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
@@ -154,6 +172,7 @@ Item {
NIcon {
icon: root.icon
pointSize: iconSize
+ applyUiScale: false
color: hovered ? Color.mOnTertiary : Color.mOnSurface
// Center horizontally
x: (iconCircle.width - width) / 2
diff --git a/Modules/Bar/Extras/BarWidgetLoader.qml b/Modules/Bar/Extras/BarWidgetLoader.qml
index 8697db5b..fff11ebf 100644
--- a/Modules/Bar/Extras/BarWidgetLoader.qml
+++ b/Modules/Bar/Extras/BarWidgetLoader.qml
@@ -16,18 +16,8 @@ Item {
implicitWidth: getImplicitSize(loader.item, "implicitWidth")
implicitHeight: getImplicitSize(loader.item, "implicitHeight")
- Connections {
- target: ScalingService
- enabled: loader.item && (loader.item.screen !== undefined)
- function onScaleChanged(aScreenName, scale) {
- if (loader.item && loader.item.screen && aScreenName === screenName) {
- loader.item['scaling'] = scale
- }
- }
- }
-
function getImplicitSize(item, prop) {
- return (item && item.visible) ? item[prop] : 0
+ return (item && item.visible) ? Math.round(item[prop]) : 0
}
Loader {
@@ -77,7 +67,7 @@ Item {
// Error handling
onWidgetIdChanged: {
if (widgetId && !BarWidgetRegistry.hasWidget(widgetId)) {
- Logger.warn("BarWidgetLoader", "Widget not found in bar registry:", widgetId)
+ Logger.warn("BarWidgetLoader", "Widget not found in registry:", widgetId)
}
}
}
diff --git a/Modules/Bar/Extras/TrayMenu.qml b/Modules/Bar/Extras/TrayMenu.qml
index ec16a860..c0f15e0e 100644
--- a/Modules/Bar/Extras/TrayMenu.qml
+++ b/Modules/Bar/Extras/TrayMenu.qml
@@ -15,23 +15,13 @@ PopupWindow {
property bool isSubMenu: false
property bool isHovered: rootMouseArea.containsMouse
property ShellScreen screen
- property real scaling: ScalingService.getScreenScale(screen)
-
- Connections {
- target: ScalingService
- function onScaleChanged(screenName, scale) {
- if ((screen != null) && (screenName === screen.name)) {
- scaling = scale
- }
- }
- }
readonly property int menuWidth: 180
- implicitWidth: menuWidth * scaling
+ implicitWidth: menuWidth
// Use the content height of the Flickable for implicit height
- implicitHeight: Math.min(screen ? screen.height * 0.9 : Screen.height * 0.9, flickable.contentHeight + (Style.marginS * 2 * scaling))
+ implicitHeight: Math.min(screen ? screen.height * 0.9 : Screen.height * 0.9, flickable.contentHeight + (Style.marginS * 2))
visible: false
color: Color.transparent
anchor.item: anchorItem
@@ -98,14 +88,14 @@ PopupWindow {
anchors.fill: parent
color: Color.mSurface
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
- radius: Style.radiusM * scaling
+ border.width: Math.max(1, Style.borderS)
+ radius: Style.radiusM
}
Flickable {
id: flickable
anchors.fill: parent
- anchors.margins: Style.marginS * scaling
+ anchors.margins: Style.marginS
contentHeight: columnLayout.implicitHeight
interactive: true
@@ -125,11 +115,11 @@ PopupWindow {
Layout.preferredWidth: parent.width
Layout.preferredHeight: {
if (modelData?.isSeparator) {
- return 8 * scaling
+ return 8
} else {
// Calculate based on text content
- const textHeight = text.contentHeight || (Style.fontSizeS * scaling * 1.2)
- return Math.max(28 * scaling, textHeight + (Style.marginS * 2 * scaling))
+ const textHeight = text.contentHeight || (Style.fontSizeS * 1.2)
+ return Math.max(28, textHeight + (Style.marginS * 2))
}
}
@@ -138,35 +128,35 @@ PopupWindow {
NDivider {
anchors.centerIn: parent
- width: parent.width - (Style.marginM * scaling * 2)
+ width: parent.width - (Style.marginM * 2)
visible: modelData?.isSeparator ?? false
}
Rectangle {
anchors.fill: parent
color: mouseArea.containsMouse ? Color.mTertiary : Color.transparent
- radius: Style.radiusS * scaling
+ radius: Style.radiusS
visible: !(modelData?.isSeparator ?? false)
RowLayout {
anchors.fill: parent
- anchors.leftMargin: Style.marginM * scaling
- anchors.rightMargin: Style.marginM * scaling
- spacing: Style.marginS * scaling
+ anchors.leftMargin: Style.marginM
+ anchors.rightMargin: Style.marginM
+ spacing: Style.marginS
NText {
id: text
Layout.fillWidth: true
color: (modelData?.enabled ?? true) ? (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) : Color.mOnSurfaceVariant
text: modelData?.text !== "" ? modelData?.text.replace(/[\n\r]+/g, ' ') : "..."
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
}
Image {
- Layout.preferredWidth: Style.marginL * scaling
- Layout.preferredHeight: Style.marginL * scaling
+ Layout.preferredWidth: Style.marginL
+ Layout.preferredHeight: Style.marginL
source: modelData?.icon ?? ""
visible: (modelData?.icon ?? "") !== ""
fillMode: Image.PreserveAspectFit
@@ -174,7 +164,8 @@ PopupWindow {
NIcon {
icon: modelData?.hasChildren ? "menu" : ""
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
+ applyUiScale: false
verticalAlignment: Text.AlignVCenter
visible: modelData?.hasChildren ?? false
color: (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface)
@@ -216,8 +207,8 @@ PopupWindow {
}
// Need a slight overlap so that menu don't close when moving the mouse to a submenu
- const submenuWidth = menuWidth * scaling // Assuming a similar width as the parent
- const overlap = 4 * scaling // A small overlap to bridge the mouse path
+ const submenuWidth = menuWidth // Assuming a similar width as the parent
+ const overlap = 4 // A small overlap to bridge the mouse path
// Determine submenu opening direction based on bar position and available space
let openLeft = false
diff --git a/Modules/Bar/WiFi/WiFiPanel.qml b/Modules/Bar/WiFi/WiFiPanel.qml
index 4bd32b47..9bab6f25 100644
--- a/Modules/Bar/WiFi/WiFiPanel.qml
+++ b/Modules/Bar/WiFi/WiFiPanel.qml
@@ -10,8 +10,8 @@ import qs.Widgets
NPanel {
id: root
- preferredWidth: 400
- preferredHeight: 500
+ preferredWidth: 400 * Style.uiScaleRatio
+ preferredHeight: 500 * Style.uiScaleRatio
panelKeyboardFocus: true
property string passwordSsid: ""
@@ -25,23 +25,23 @@ NPanel {
ColumnLayout {
anchors.fill: parent
- anchors.margins: Style.marginL * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginL
+ spacing: Style.marginM
// Header
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NIcon {
icon: Settings.data.network.wifiEnabled ? "wifi" : "wifi-off"
- pointSize: Style.fontSizeXXL * scaling
+ pointSize: Style.fontSizeXXL
color: Settings.data.network.wifiEnabled ? Color.mPrimary : Color.mOnSurfaceVariant
}
NText {
text: I18n.tr("wifi.panel.title")
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.fillWidth: true
@@ -51,7 +51,7 @@ NPanel {
id: wifiSwitch
checked: Settings.data.network.wifiEnabled
onToggled: checked => NetworkService.setWifiEnabled(checked)
- baseSize: Style.baseWidgetSize * 0.65 * scaling
+ baseSize: Style.baseWidgetSize * 0.65
}
NIconButton {
@@ -78,28 +78,28 @@ NPanel {
Rectangle {
visible: NetworkService.lastError.length > 0
Layout.fillWidth: true
- Layout.preferredHeight: errorRow.implicitHeight + (Style.marginM * scaling * 2)
+ Layout.preferredHeight: errorRow.implicitHeight + (Style.marginM * 2)
color: Qt.rgba(Color.mError.r, Color.mError.g, Color.mError.b, 0.1)
- radius: Style.radiusS * scaling
- border.width: Math.max(1, Style.borderS * scaling)
+ radius: Style.radiusS
+ border.width: Math.max(1, Style.borderS)
border.color: Color.mError
RowLayout {
id: errorRow
anchors.fill: parent
- anchors.margins: Style.marginM * scaling
- spacing: Style.marginS * scaling
+ anchors.margins: Style.marginM
+ spacing: Style.marginS
NIcon {
icon: "warning"
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
color: Color.mError
}
NText {
text: NetworkService.lastError
color: Color.mError
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
wrapMode: Text.Wrap
Layout.fillWidth: true
}
@@ -122,7 +122,7 @@ NPanel {
ColumnLayout {
visible: !Settings.data.network.wifiEnabled
anchors.fill: parent
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
Item {
Layout.fillHeight: true
@@ -130,21 +130,21 @@ NPanel {
NIcon {
icon: "wifi-off"
- pointSize: 64 * scaling
+ pointSize: 64
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: I18n.tr("wifi.panel.disabled")
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: I18n.tr("wifi.panel.enable-message")
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
@@ -158,7 +158,7 @@ NPanel {
ColumnLayout {
visible: Settings.data.network.wifiEnabled && NetworkService.scanning && Object.keys(NetworkService.networks).length === 0
anchors.fill: parent
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Item {
Layout.fillHeight: true
@@ -167,13 +167,13 @@ NPanel {
NBusyIndicator {
running: true
color: Color.mPrimary
- size: Style.baseWidgetSize * scaling
+ size: Style.baseWidgetSize
Layout.alignment: Qt.AlignHCenter
}
NText {
text: I18n.tr("wifi.panel.searching")
- pointSize: Style.fontSizeNormal * scaling
+ pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
@@ -193,7 +193,7 @@ NPanel {
ColumnLayout {
width: parent.width
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Network list
Repeater {
@@ -211,14 +211,14 @@ NPanel {
Rectangle {
Layout.fillWidth: true
- implicitHeight: netColumn.implicitHeight + (Style.marginM * scaling * 2)
- radius: Style.radiusM * scaling
+ implicitHeight: netColumn.implicitHeight + (Style.marginM * 2)
+ radius: Style.radiusM
// Add opacity for operations in progress
opacity: (NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid) ? 0.6 : 1.0
color: modelData.connected ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.05) : Color.mSurface
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
border.color: modelData.connected ? Color.mPrimary : Color.mOutline
// Smooth opacity animation
@@ -230,29 +230,29 @@ NPanel {
ColumnLayout {
id: netColumn
- width: parent.width - (Style.marginM * scaling * 2)
- x: Style.marginM * scaling
- y: Style.marginM * scaling
- spacing: Style.marginS * scaling
+ width: parent.width - (Style.marginM * 2)
+ x: Style.marginM
+ y: Style.marginM
+ spacing: Style.marginS
// Main row
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
NIcon {
icon: NetworkService.signalIcon(modelData.signal)
- pointSize: Style.fontSizeXXL * scaling
+ pointSize: Style.fontSizeXXL
color: modelData.connected ? Color.mPrimary : Color.mOnSurface
}
ColumnLayout {
Layout.fillWidth: true
- spacing: 2 * scaling
+ spacing: 2
NText {
text: modelData.ssid
- pointSize: Style.fontSizeNormal * scaling
+ pointSize: Style.fontSizeM
font.weight: modelData.connected ? Style.fontWeightBold : Style.fontWeightMedium
color: Color.mOnSurface
elide: Text.ElideRight
@@ -260,30 +260,30 @@ NPanel {
}
RowLayout {
- spacing: Style.marginXS * scaling
+ spacing: Style.marginXS
NText {
text: I18n.tr("system.signal-strength", {
"signal": modelData.signal
})
- pointSize: Style.fontSizeXXS * scaling
+ pointSize: Style.fontSizeXXS
color: Color.mOnSurfaceVariant
}
NText {
text: "•"
- pointSize: Style.fontSizeXXS * scaling
+ pointSize: Style.fontSizeXXS
color: Color.mOnSurfaceVariant
}
NText {
text: NetworkService.isSecured(modelData.security) ? modelData.security : "Open"
- pointSize: Style.fontSizeXXS * scaling
+ pointSize: Style.fontSizeXXS
color: Color.mOnSurfaceVariant
}
Item {
- Layout.preferredWidth: Style.marginXXS * scaling
+ Layout.preferredWidth: Style.marginXXS
}
// Update the status badges area (around line 237)
@@ -291,14 +291,14 @@ NPanel {
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
color: Color.mPrimary
radius: height * 0.5
- width: connectedText.implicitWidth + (Style.marginS * scaling * 2)
- height: connectedText.implicitHeight + (Style.marginXXS * scaling * 2)
+ width: connectedText.implicitWidth + (Style.marginS * 2)
+ height: connectedText.implicitHeight + (Style.marginXXS * 2)
NText {
id: connectedText
anchors.centerIn: parent
text: I18n.tr("wifi.panel.connected")
- pointSize: Style.fontSizeXXS * scaling
+ pointSize: Style.fontSizeXXS
color: Color.mOnPrimary
}
}
@@ -307,14 +307,14 @@ NPanel {
visible: NetworkService.disconnectingFrom === modelData.ssid
color: Color.mError
radius: height * 0.5
- width: disconnectingText.implicitWidth + (Style.marginS * scaling * 2)
- height: disconnectingText.implicitHeight + (Style.marginXXS * scaling * 2)
+ width: disconnectingText.implicitWidth + (Style.marginS * 2)
+ height: disconnectingText.implicitHeight + (Style.marginXXS * 2)
NText {
id: disconnectingText
anchors.centerIn: parent
text: I18n.tr("wifi.panel.disconnecting")
- pointSize: Style.fontSizeXXS * scaling
+ pointSize: Style.fontSizeXXS
color: Color.mOnPrimary
}
}
@@ -323,14 +323,14 @@ NPanel {
visible: NetworkService.forgettingNetwork === modelData.ssid
color: Color.mError
radius: height * 0.5
- width: forgettingText.implicitWidth + (Style.marginS * scaling * 2)
- height: forgettingText.implicitHeight + (Style.marginXXS * scaling * 2)
+ width: forgettingText.implicitWidth + (Style.marginS * 2)
+ height: forgettingText.implicitHeight + (Style.marginXXS * 2)
NText {
id: forgettingText
anchors.centerIn: parent
text: I18n.tr("wifi.panel.forgetting")
- pointSize: Style.fontSizeXXS * scaling
+ pointSize: Style.fontSizeXXS
color: Color.mOnPrimary
}
}
@@ -339,16 +339,16 @@ NPanel {
visible: modelData.cached && !modelData.connected && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
color: Color.transparent
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
radius: height * 0.5
- width: savedText.implicitWidth + (Style.marginS * scaling * 2)
- height: savedText.implicitHeight + (Style.marginXXS * scaling * 2)
+ width: savedText.implicitWidth + (Style.marginS * 2)
+ height: savedText.implicitHeight + (Style.marginXXS * 2)
NText {
id: savedText
anchors.centerIn: parent
text: I18n.tr("wifi.panel.saved")
- pointSize: Style.fontSizeXXS * scaling
+ pointSize: Style.fontSizeXXS
color: Color.mOnSurfaceVariant
}
}
@@ -357,13 +357,13 @@ NPanel {
// Action area
RowLayout {
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
NBusyIndicator {
visible: NetworkService.connectingTo === modelData.ssid || NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid
running: visible
color: Color.mPrimary
- size: Style.baseWidgetSize * 0.5 * scaling
+ size: Style.baseWidgetSize * 0.5
}
NIconButton {
@@ -384,7 +384,7 @@ NPanel {
return I18n.tr("wifi.panel.password")
}
outlined: !hovered
- fontSize: Style.fontSizeXS * scaling
+ fontSize: Style.fontSizeXS
enabled: !NetworkService.connecting
onClicked: {
if (modelData.existing || modelData.cached || !NetworkService.isSecured(modelData.security)) {
@@ -401,7 +401,7 @@ NPanel {
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
text: I18n.tr("wifi.panel.disconnect")
outlined: !hovered
- fontSize: Style.fontSizeXS * scaling
+ fontSize: Style.fontSizeXS
backgroundColor: Color.mError
onClicked: NetworkService.disconnect(modelData.ssid)
}
@@ -412,35 +412,35 @@ NPanel {
Rectangle {
visible: passwordSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
Layout.fillWidth: true
- height: passwordRow.implicitHeight + Style.marginS * scaling * 2
+ height: passwordRow.implicitHeight + Style.marginS * 2
color: Color.mSurfaceVariant
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
- radius: Style.radiusS * scaling
+ border.width: Math.max(1, Style.borderS)
+ radius: Style.radiusS
RowLayout {
id: passwordRow
anchors.fill: parent
- anchors.margins: Style.marginS * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginS
+ spacing: Style.marginM
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
- radius: Style.radiusXS * scaling
+ radius: Style.radiusXS
color: Color.mSurface
border.color: pwdInput.activeFocus ? Color.mSecondary : Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
TextInput {
id: pwdInput
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
- anchors.margins: Style.marginS * scaling
+ anchors.margins: Style.marginS
text: passwordInput
font.family: Settings.data.ui.fontFixed
- font.pointSize: Style.fontSizeS * scaling
+ font.pointSize: Style.fontSizeS
color: Color.mOnSurface
echoMode: TextInput.Password
selectByMouse: true
@@ -462,14 +462,14 @@ NPanel {
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("wifi.panel.enter-password")
color: Color.mOnSurfaceVariant
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
}
}
}
NButton {
text: I18n.tr("wifi.panel.connect")
- fontSize: Style.fontSizeXXS * scaling
+ fontSize: Style.fontSizeXXS
enabled: passwordInput.length > 0 && !NetworkService.connecting
outlined: true
onClicked: {
@@ -494,28 +494,28 @@ NPanel {
Rectangle {
visible: expandedSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
Layout.fillWidth: true
- height: forgetRow.implicitHeight + Style.marginS * 2 * scaling
+ height: forgetRow.implicitHeight + Style.marginS * 2
color: Color.mSurfaceVariant
- radius: Style.radiusS * scaling
- border.width: Math.max(1, Style.borderS * scaling)
+ radius: Style.radiusS
+ border.width: Math.max(1, Style.borderS)
border.color: Color.mOutline
RowLayout {
id: forgetRow
anchors.fill: parent
- anchors.margins: Style.marginS * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginS
+ spacing: Style.marginM
RowLayout {
NIcon {
icon: "trash"
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
color: Color.mError
}
NText {
text: I18n.tr("wifi.panel.forget-network")
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
color: Color.mError
Layout.fillWidth: true
}
@@ -524,7 +524,7 @@ NPanel {
NButton {
id: forgetButton
text: I18n.tr("wifi.panel.forget")
- fontSize: Style.fontSizeXXS * scaling
+ fontSize: Style.fontSizeXXS
backgroundColor: Color.mError
outlined: forgetButton.hovered ? false : true
onClicked: {
@@ -550,7 +550,7 @@ NPanel {
ColumnLayout {
visible: Settings.data.network.wifiEnabled && !NetworkService.scanning && Object.keys(NetworkService.networks).length === 0
anchors.fill: parent
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Item {
Layout.fillHeight: true
@@ -558,14 +558,14 @@ NPanel {
NIcon {
icon: "search"
- pointSize: 64 * scaling
+ pointSize: 64
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: I18n.tr("wifi.panel.no-networks")
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml
index 91c05f1d..745f69b5 100644
--- a/Modules/Bar/Widgets/ActiveWindow.qml
+++ b/Modules/Bar/Widgets/ActiveWindow.qml
@@ -10,8 +10,8 @@ import qs.Widgets
Item {
id: root
+
property ShellScreen screen
- property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
@@ -30,23 +30,23 @@ Item {
return {}
}
- readonly property bool hasActiveWindow: CompositorService.getFocusedWindowTitle() !== ""
- readonly property string windowTitle: CompositorService.getFocusedWindowTitle() || "No active window"
- readonly property string fallbackIcon: "user-desktop"
-
- readonly property string barPosition: Settings.data.bar.position
- readonly property bool compact: (Settings.data.bar.density === "compact")
-
// Widget settings - matching MediaMini pattern
readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon
- readonly property bool autoHide: (widgetSettings.autoHide !== undefined) ? widgetSettings.autoHide : widgetMetadata.autoHide
+ readonly property string hideMode: (widgetSettings.hideMode !== undefined) ? widgetSettings.hideMode : widgetMetadata.hideMode
readonly property string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : (widgetMetadata.scrollingMode !== undefined ? widgetMetadata.scrollingMode : "hover")
readonly property int widgetWidth: (widgetSettings.width !== undefined) ? widgetSettings.width : Math.max(widgetMetadata.width, screen.width * 0.06)
- implicitHeight: visible ? ((barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling)) : 0
- implicitWidth: visible ? ((barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (widgetWidth * scaling)) : 0
+ readonly property bool isVerticalBar: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right")
+ readonly property bool hasFocusedWindow: CompositorService.getFocusedWindow() !== null
+ readonly property string windowTitle: CompositorService.getFocusedWindowTitle() || "No active window"
+ readonly property string fallbackIcon: "user-desktop"
- opacity: !autoHide || hasActiveWindow ? 1.0 : 0
+ implicitHeight: visible ? (isVerticalBar ? calculatedVerticalDimension() : Style.barHeight) : 0
+ implicitWidth: visible ? (isVerticalBar ? calculatedVerticalDimension() : widgetWidth) : 0
+
+ // "visible": Always Visible, "hidden": Hide When Empty, "transparent": Transparent When Empty
+ visible: hideMode !== "hidden" || hasFocusedWindow
+ opacity: hideMode !== "transparent" || hasFocusedWindow ? 1.0 : 0
Behavior on opacity {
NumberAnimation {
duration: Style.animationNormal
@@ -54,8 +54,9 @@ Item {
}
}
- function calculatedVerticalHeight() {
- return Math.round(Style.baseWidgetSize * 0.8 * scaling)
+ function calculatedVerticalDimension() {
+ const ratio = (Settings.data.bar.density === "mini") ? 0.67 : 0.8
+ return Math.round(Style.baseWidgetSize * ratio)
}
function getAppIcon() {
@@ -106,7 +107,8 @@ Item {
id: fullTitleMetrics
visible: false
text: windowTitle
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
+ applyUiScale: false
font.weight: Style.fontWeightMedium
}
@@ -115,29 +117,29 @@ Item {
visible: root.visible
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
- width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (widgetWidth * scaling)
- height: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : Math.round(Style.capsuleHeight * scaling)
- radius: (barPosition === "left" || barPosition === "right") ? width / 2 : Math.round(Style.radiusM * scaling)
+ width: isVerticalBar ? root.width : widgetWidth
+ height: isVerticalBar ? width : Style.capsuleHeight
+ radius: isVerticalBar ? width / 2 : Style.radiusM
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
Item {
id: mainContainer
anchors.fill: parent
- anchors.leftMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
- anchors.rightMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
+ anchors.leftMargin: isVerticalBar ? 0 : Style.marginS
+ anchors.rightMargin: isVerticalBar ? 0 : Style.marginS
// Horizontal layout for top/bottom bars
RowLayout {
id: rowLayout
anchors.verticalCenter: parent.verticalCenter
- spacing: Style.marginS * scaling
- visible: barPosition === "top" || barPosition === "bottom"
+ spacing: Style.marginS
+ visible: !isVerticalBar
z: 1
// Window icon
Item {
- Layout.preferredWidth: Math.round(18 * scaling)
- Layout.preferredHeight: Math.round(18 * scaling)
+ Layout.preferredWidth: 18
+ Layout.preferredHeight: 18
Layout.alignment: Qt.AlignVCenter
visible: showIcon
@@ -148,6 +150,15 @@ Item {
asynchronous: true
smooth: true
visible: source !== ""
+
+ // Apply dock shader to active window icon (always themed)
+ layer.enabled: widgetSettings.colorizeIcons !== false
+ layer.effect: ShaderEffect {
+ property color targetColor: Color.mOnSurface
+ property real colorizeMode: 0.0 // Dock mode (grayscale)
+
+ fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb")
+ }
}
}
@@ -156,10 +167,10 @@ Item {
id: titleContainer
Layout.preferredWidth: {
// Calculate available width based on other elements
- var iconWidth = (showIcon && windowIcon.visible ? (18 * scaling + Style.marginS * scaling) : 0)
- var totalMargins = Style.marginXXS * scaling * 2
+ var iconWidth = (showIcon && windowIcon.visible ? (18 + Style.marginS) : 0)
+ var totalMargins = Style.marginXXS * 2
var availableWidth = mainContainer.width - iconWidth - totalMargins
- return Math.max(20 * scaling, availableWidth)
+ return Math.max(20, availableWidth)
}
Layout.maximumWidth: Layout.preferredWidth
Layout.alignment: Qt.AlignVCenter
@@ -238,12 +249,13 @@ Item {
x: scrollX
RowLayout {
- spacing: 50 * scaling // Gap between text copies
+ spacing: 50 // Gap between text copies
NText {
id: titleText
text: windowTitle
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
+ applyUiScale: false
font.weight: Style.fontWeightMedium
verticalAlignment: Text.AlignVCenter
color: Color.mOnSurface
@@ -253,6 +265,8 @@ Item {
NText {
text: windowTitle
font: titleText.font
+ pointSize: Style.fontSizeS
+ applyUiScale: false
verticalAlignment: Text.AlignVCenter
color: Color.mOnSurface
visible: titleContainer.needsScrolling && titleContainer.isScrolling
@@ -275,7 +289,7 @@ Item {
id: infiniteScroll
running: titleContainer.isScrolling && !titleContainer.isResetting
from: 0
- to: -(titleContainer.textWidth + 50 * scaling)
+ to: -(titleContainer.textWidth + 50)
duration: Math.max(4000, windowTitle.length * 100)
loops: Animation.Infinite
easing.type: Easing.Linear
@@ -295,15 +309,15 @@ Item {
Item {
id: verticalLayout
anchors.centerIn: parent
- width: parent.width - Style.marginM * scaling * 2
- height: parent.height - Style.marginM * scaling * 2
- visible: barPosition === "left" || barPosition === "right"
+ width: parent.width - Style.marginM * 2
+ height: parent.height - Style.marginM * 2
+ visible: isVerticalBar
z: 1
// Window icon
Item {
- width: Style.baseWidgetSize * 0.5 * scaling
- height: Style.baseWidgetSize * 0.5 * scaling
+ width: Style.baseWidgetSize * 0.5
+ height: width
anchors.centerIn: parent
visible: windowTitle !== ""
@@ -314,6 +328,15 @@ Item {
asynchronous: true
smooth: true
visible: source !== ""
+
+ // Apply dock shader to active window icon (always themed)
+ layer.enabled: widgetSettings.colorizeIcons !== false
+ layer.effect: ShaderEffect {
+ property color targetColor: Color.mOnSurface
+ property real colorizeMode: 0.0 // Dock mode (grayscale)
+
+ fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb")
+ }
}
}
}
@@ -326,7 +349,7 @@ Item {
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onEntered: {
- if ((windowTitle !== "") && (barPosition === "left" || barPosition === "right") || (scrollingMode === "never")) {
+ if ((windowTitle !== "") && isVerticalBar || (scrollingMode === "never")) {
TooltipService.show(Screen, root, windowTitle, BarService.getTooltipDirection())
}
}
diff --git a/Modules/Bar/Widgets/Battery.qml b/Modules/Bar/Widgets/Battery.qml
index 83a6e850..47676cdb 100644
--- a/Modules/Bar/Widgets/Battery.qml
+++ b/Modules/Bar/Widgets/Battery.qml
@@ -11,7 +11,6 @@ Item {
id: root
property ShellScreen screen
- property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
@@ -87,7 +86,7 @@ Item {
BarPill {
id: pill
- compact: (Settings.data.bar.density === "compact")
+ density: Settings.data.bar.density
rightOpen: BarService.getPillDirection(root)
icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent, charging, isReady)
text: (isReady || testMode) ? Math.round(percent) : "-"
diff --git a/Modules/Bar/Widgets/Bluetooth.qml b/Modules/Bar/Widgets/Bluetooth.qml
index 21fe96c7..e6f50f0f 100644
--- a/Modules/Bar/Widgets/Bluetooth.qml
+++ b/Modules/Bar/Widgets/Bluetooth.qml
@@ -10,10 +10,11 @@ import qs.Widgets
NIconButton {
id: root
- property real scaling: 1.0
+ property ShellScreen screen
baseSize: Style.capsuleHeight
- compact: (Settings.data.bar.density === "compact")
+ applyUiScale: false
+ density: Settings.data.bar.density
colorBg: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
colorFg: Color.mOnSurface
colorBorder: Color.transparent
diff --git a/Modules/Bar/Widgets/Brightness.qml b/Modules/Bar/Widgets/Brightness.qml
index 652660c0..5c02e490 100644
--- a/Modules/Bar/Widgets/Brightness.qml
+++ b/Modules/Bar/Widgets/Brightness.qml
@@ -10,7 +10,6 @@ Item {
id: root
property ShellScreen screen
- property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
@@ -77,7 +76,7 @@ Item {
BarPill {
id: pill
- compact: (Settings.data.bar.density === "compact")
+ density: Settings.data.bar.density
rightOpen: BarService.getPillDirection(root)
icon: getIcon()
autoHide: false // Important to be false so we can hover as long as we want
diff --git a/Modules/Bar/Widgets/Clock.qml b/Modules/Bar/Widgets/Clock.qml
index 26ae5762..cbbecd2f 100644
--- a/Modules/Bar/Widgets/Clock.qml
+++ b/Modules/Bar/Widgets/Clock.qml
@@ -9,7 +9,6 @@ Rectangle {
id: root
property ShellScreen screen
- property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
@@ -30,7 +29,7 @@ Rectangle {
readonly property string barPosition: Settings.data.bar.position
readonly property bool isBarVertical: barPosition === "left" || barPosition === "right"
- readonly property bool compact: (Settings.data.bar.density === "compact")
+ readonly property bool density: Settings.data.bar.density
readonly property var now: Time.date
@@ -41,11 +40,11 @@ Rectangle {
readonly property string formatHorizontal: widgetSettings.formatHorizontal !== undefined ? widgetSettings.formatHorizontal : widgetMetadata.formatHorizontal
readonly property string formatVertical: widgetSettings.formatVertical !== undefined ? widgetSettings.formatVertical : widgetMetadata.formatVertical
- implicitWidth: isBarVertical ? Math.round(Style.capsuleHeight * scaling) : Math.round((isBarVertical ? verticalLoader.implicitWidth : horizontalLoader.implicitWidth) + Style.marginM * 2 * scaling)
+ implicitWidth: isBarVertical ? Style.capsuleHeight : Math.round((isBarVertical ? verticalLoader.implicitWidth : horizontalLoader.implicitWidth) + Style.marginM * 2)
- implicitHeight: isBarVertical ? Math.round(verticalLoader.implicitHeight + Style.marginS * 2 * scaling) : Math.round(Style.capsuleHeight * scaling)
+ implicitHeight: isBarVertical ? Math.round(verticalLoader.implicitHeight + Style.marginS * 2) : Style.capsuleHeight
- radius: Math.round(Style.radiusS * scaling)
+ radius: Style.radiusS
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
Item {
@@ -59,7 +58,7 @@ Rectangle {
anchors.centerIn: parent
sourceComponent: ColumnLayout {
anchors.centerIn: parent
- spacing: Settings.data.bar.showCapsule ? -4 * scaling : -2 * scaling
+ spacing: Settings.data.bar.showCapsule ? -4 : -2
Repeater {
id: repeater
model: Qt.locale().toString(now, formatHorizontal.trim()).split("\\n")
@@ -69,11 +68,12 @@ Rectangle {
family: useCustomFont && customFont ? customFont : Settings.data.ui.fontDefault
pointSize: {
if (repeater.model.length == 1) {
- return Style.fontSizeS * scaling
+ return Style.fontSizeS
} else {
- return (index == 0) ? Style.fontSizeXS * scaling : Style.fontSizeXXS * scaling
+ return (index == 0) ? Style.fontSizeXS : Style.fontSizeXXS
}
}
+ applyUiScale: false
font.weight: Style.fontWeightBold
color: usePrimaryColor ? Color.mPrimary : Color.mOnSurface
wrapMode: Text.WordWrap
@@ -90,14 +90,15 @@ Rectangle {
anchors.centerIn: parent // Now this works without layout conflicts
sourceComponent: ColumnLayout {
anchors.centerIn: parent
- spacing: -2 * scaling
+ spacing: -2
Repeater {
model: Qt.locale().toString(now, formatVertical.trim()).split(" ")
delegate: NText {
visible: text !== ""
text: modelData
family: useCustomFont && customFont ? customFont : Settings.data.ui.fontDefault
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
+ applyUiScale: false
font.weight: Style.fontWeightBold
color: usePrimaryColor ? Color.mPrimary : Color.mOnSurface
wrapMode: Text.WordWrap
diff --git a/Modules/Bar/Widgets/ControlCenter.qml b/Modules/Bar/Widgets/ControlCenter.qml
index 5fbe7282..b0ceea69 100644
--- a/Modules/Bar/Widgets/ControlCenter.qml
+++ b/Modules/Bar/Widgets/ControlCenter.qml
@@ -9,7 +9,7 @@ import qs.Services
NIconButton {
id: root
- property real scaling: 1.0
+ property ShellScreen screen
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
@@ -37,7 +37,8 @@ NIconButton {
tooltipText: I18n.tr("tooltips.open-control-center")
tooltipDirection: BarService.getTooltipDirection()
baseSize: Style.capsuleHeight
- compact: (Settings.data.bar.density === "compact")
+ applyUiScale: false
+ density: Settings.data.bar.density
colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: Color.mOnSurface
colorBgHover: useDistroLogo ? Color.mSurfaceVariant : Color.mTertiary
diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml
index 3973b4cd..af0e58cd 100644
--- a/Modules/Bar/Widgets/CustomButton.qml
+++ b/Modules/Bar/Widgets/CustomButton.qml
@@ -11,9 +11,7 @@ import qs.Modules.Bar.Extras
Item {
id: root
- // Widget properties passed from Bar.qml
- property var screen
- property real scaling: 1.0
+ property ShellScreen screen
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
@@ -50,7 +48,7 @@ Item {
rightOpen: BarService.getPillDirection(root)
icon: customIcon
text: _dynamicText
- compact: (Settings.data.bar.density === "compact")
+ density: Settings.data.bar.density
autoHide: false
forceOpen: _dynamicText !== ""
forceClose: false
diff --git a/Modules/Bar/Widgets/DarkMode.qml b/Modules/Bar/Widgets/DarkMode.qml
index 1333da05..d9a2cb25 100644
--- a/Modules/Bar/Widgets/DarkMode.qml
+++ b/Modules/Bar/Widgets/DarkMode.qml
@@ -6,13 +6,14 @@ import qs.Services
NIconButton {
id: root
- property real scaling: 1.0
+ property ShellScreen screen
icon: "dark-mode"
tooltipText: Settings.data.colorSchemes.darkMode ? I18n.tr("tooltips.switch-to-light-mode") : I18n.tr("tooltips.switch-to-dark-mode")
tooltipDirection: BarService.getTooltipDirection()
- compact: (Settings.data.bar.density === "compact")
+ density: Settings.data.bar.density
baseSize: Style.capsuleHeight
+ applyUiScale: false
colorBg: Settings.data.colorSchemes.darkMode ? (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent) : Color.mPrimary
colorFg: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mOnPrimary
colorBorder: Color.transparent
diff --git a/Modules/Bar/Widgets/KeepAwake.qml b/Modules/Bar/Widgets/KeepAwake.qml
index 692466a4..c0546884 100644
--- a/Modules/Bar/Widgets/KeepAwake.qml
+++ b/Modules/Bar/Widgets/KeepAwake.qml
@@ -8,10 +8,11 @@ import qs.Widgets
NIconButton {
id: root
- property real scaling: 1.0
+ property ShellScreen screen
baseSize: Style.capsuleHeight
- compact: (Settings.data.bar.density === "compact")
+ applyUiScale: false
+ density: Settings.data.bar.density
icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off"
tooltipText: IdleInhibitorService.isInhibited ? I18n.tr("tooltips.disable-keep-awake") : I18n.tr("tooltips.enable-keep-awake")
tooltipDirection: BarService.getTooltipDirection()
diff --git a/Modules/Bar/Widgets/KeyboardLayout.qml b/Modules/Bar/Widgets/KeyboardLayout.qml
index ec83b71e..6d597227 100644
--- a/Modules/Bar/Widgets/KeyboardLayout.qml
+++ b/Modules/Bar/Widgets/KeyboardLayout.qml
@@ -12,7 +12,6 @@ Item {
id: root
property ShellScreen screen
- property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
@@ -43,7 +42,7 @@ Item {
id: pill
anchors.verticalCenter: parent.verticalCenter
- compact: (Settings.data.bar.density === "compact")
+ density: Settings.data.bar.density
rightOpen: BarService.getPillDirection(root)
icon: "keyboard"
autoHide: false // Important to be false so we can hover as long as we want
diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml
index e33be8f2..922316f4 100644
--- a/Modules/Bar/Widgets/MediaMini.qml
+++ b/Modules/Bar/Widgets/MediaMini.qml
@@ -11,7 +11,6 @@ Item {
id: root
property ShellScreen screen
- property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
@@ -30,10 +29,9 @@ Item {
return {}
}
- readonly property string barPosition: Settings.data.bar.position
- readonly property bool compact: (Settings.data.bar.density === "compact")
+ readonly property bool isVerticalBar: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right")
- readonly property bool autoHide: (widgetSettings.autoHide !== undefined) ? widgetSettings.autoHide : widgetMetadata.autoHide
+ readonly property string hideMode: (widgetSettings.hideMode !== undefined) ? widgetSettings.hideMode : "hidden" // "visible", "hidden", "transparent"
readonly property bool showAlbumArt: (widgetSettings.showAlbumArt !== undefined) ? widgetSettings.showAlbumArt : widgetMetadata.showAlbumArt
readonly property bool showVisualizer: (widgetSettings.showVisualizer !== undefined) ? widgetSettings.showVisualizer : widgetMetadata.showVisualizer
readonly property string visualizerType: (widgetSettings.visualizerType !== undefined && widgetSettings.visualizerType !== "") ? widgetSettings.visualizerType : widgetMetadata.visualizerType
@@ -42,7 +40,7 @@ Item {
// Fixed width - no expansion
readonly property real widgetWidth: Math.max(145, screen.width * 0.06)
- readonly property bool hasActivePlayer: MediaService.currentPlayer !== null && getTitle() !== ""
+ readonly property bool hasActivePlayer: MediaService.currentPlayer !== null
readonly property string placeholderText: I18n.tr("bar.widget-settings.media-mini.no-active-player")
readonly property string tooltipText: {
@@ -60,10 +58,12 @@ Item {
return title
}
- implicitHeight: visible ? ((barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling)) : 0
- implicitWidth: visible ? ((barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (widgetWidth * scaling)) : 0
+ implicitHeight: visible ? (isVerticalBar ? calculatedVerticalDimension() : Style.barHeight) : 0
+ implicitWidth: visible ? (isVerticalBar ? calculatedVerticalDimension() : widgetWidth) : 0
- opacity: !autoHide || hasActivePlayer || (!hasActivePlayer && !autoHide) ? 1.0 : 0
+ // "visible": Always Visible, "hidden": Hide When Empty, "transparent": Transparent When Empty
+ visible: hideMode !== "hidden" || hasActivePlayer
+ opacity: hideMode !== "transparent" || hasActivePlayer ? 1.0 : 0
Behavior on opacity {
NumberAnimation {
duration: Style.animationNormal
@@ -75,8 +75,9 @@ Item {
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "")
}
- function calculatedVerticalHeight() {
- return Math.round(Style.baseWidgetSize * 0.8 * scaling)
+ function calculatedVerticalDimension() {
+ const ratio = (Settings.data.bar.density === "mini") ? 0.67 : 0.8
+ return Math.round(Style.baseWidgetSize * ratio)
}
// A hidden text element to safely measure the full title width
@@ -85,6 +86,7 @@ Item {
visible: false
text: titleText.text
font: titleText.font
+ applyUiScale: false
}
Rectangle {
@@ -92,16 +94,16 @@ Item {
visible: root.visible
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
- width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (widgetWidth * scaling)
- height: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : Math.round(Style.capsuleHeight * scaling)
- radius: (barPosition === "left" || barPosition === "right") ? width / 2 : Math.round(Style.radiusM * scaling)
+ width: isVerticalBar ? root.width : (widgetWidth)
+ height: isVerticalBar ? width : Style.capsuleHeight
+ radius: isVerticalBar ? width / 2 : Style.radiusM
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
Item {
id: mainContainer
anchors.fill: parent
- anchors.leftMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
- anchors.rightMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
+ anchors.leftMargin: isVerticalBar ? 0 : Style.marginS
+ anchors.rightMargin: isVerticalBar ? 0 : Style.marginS
Loader {
anchors.verticalCenter: parent.verticalCenter
@@ -110,8 +112,8 @@ Item {
z: 0
sourceComponent: LinearSpectrum {
- width: mainContainer.width - Style.marginS * scaling
- height: 20 * scaling
+ width: mainContainer.width - Style.marginS
+ height: 20
values: CavaService.values
fillColor: Color.mPrimary
opacity: 0.4
@@ -125,8 +127,8 @@ Item {
z: 0
sourceComponent: MirroredSpectrum {
- width: mainContainer.width - Style.marginS * scaling
- height: mainContainer.height - Style.marginS * scaling
+ width: mainContainer.width - Style.marginS
+ height: mainContainer.height - Style.marginS
values: CavaService.values
fillColor: Color.mPrimary
opacity: 0.4
@@ -140,8 +142,8 @@ Item {
z: 0
sourceComponent: WaveSpectrum {
- width: mainContainer.width - Style.marginS * scaling
- height: mainContainer.height - Style.marginS * scaling
+ width: mainContainer.width - Style.marginS
+ height: mainContainer.height - Style.marginS
values: CavaService.values
fillColor: Color.mPrimary
opacity: 0.4
@@ -153,15 +155,15 @@ Item {
id: rowLayout
anchors.verticalCenter: parent.verticalCenter
- spacing: Style.marginS * scaling
- visible: (barPosition === "top" || barPosition === "bottom")
+ spacing: Style.marginS
+ visible: !isVerticalBar
z: 1 // Above the visualizer
NIcon {
id: windowIcon
icon: hasActivePlayer ? (MediaService.isPlaying ? "media-pause" : "media-play") : "disc"
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
verticalAlignment: Text.AlignVCenter
Layout.alignment: Qt.AlignVCenter
visible: !hasActivePlayer || (!showAlbumArt && !trackArt.visible)
@@ -173,15 +175,15 @@ Item {
spacing: 0
Item {
- Layout.preferredWidth: Math.round(21 * scaling)
- Layout.preferredHeight: Math.round(21 * scaling)
+ Layout.preferredWidth: Math.round(21 * Style.uiScaleRatio)
+ Layout.preferredHeight: Math.round(21 * Style.uiScaleRatio)
NImageCircled {
id: trackArt
anchors.fill: parent
imagePath: MediaService.trackArtUrl
fallbackIcon: MediaService.isPlaying ? "media-pause" : "media-play"
- fallbackIconSize: 10 * scaling
+ fallbackIconSize: 10
borderWidth: 0
border.color: Color.transparent
}
@@ -192,11 +194,11 @@ Item {
id: titleContainer
Layout.preferredWidth: {
// Calculate available width based on other elements in the row
- var iconWidth = (windowIcon.visible ? (Style.fontSizeL * scaling + Style.marginS * scaling) : 0)
- var albumArtWidth = (hasActivePlayer && showAlbumArt ? (18 * scaling + Style.marginS * scaling) : 0)
- var totalMargins = Style.marginXXS * scaling * 2
+ var iconWidth = (windowIcon.visible ? (Style.fontSizeL + Style.marginS) : 0)
+ var albumArtWidth = (hasActivePlayer && showAlbumArt ? (18 + Style.marginS) : 0)
+ var totalMargins = Style.marginXXS * 2
var availableWidth = mainContainer.width - iconWidth - albumArtWidth - totalMargins
- return Math.max(20 * scaling, availableWidth)
+ return Math.max(20, availableWidth)
}
Layout.maximumWidth: Layout.preferredWidth
Layout.alignment: Qt.AlignVCenter
@@ -281,12 +283,13 @@ Item {
x: scrollX
RowLayout {
- spacing: 50 * scaling // Gap between text copies
+ spacing: 50 // Gap between text copies
NText {
id: titleText
text: hasActivePlayer ? getTitle() : placeholderText
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
+ applyUiScale: false
font.weight: Style.fontWeightMedium
verticalAlignment: Text.AlignVCenter
horizontalAlignment: hasActivePlayer ? Text.AlignLeft : Text.AlignHCenter
@@ -296,6 +299,7 @@ Item {
NText {
text: hasActivePlayer ? getTitle() : placeholderText
font: titleText.font
+ applyUiScale: false
verticalAlignment: Text.AlignVCenter
horizontalAlignment: hasActivePlayer ? Text.AlignLeft : Text.AlignHCenter
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
@@ -319,7 +323,7 @@ Item {
id: infiniteScroll
running: titleContainer.isScrolling && !titleContainer.isResetting
from: 0
- to: -(titleContainer.textWidth + 50 * scaling) // Scroll one complete text width + gap
+ to: -(titleContainer.textWidth + 50) // Scroll one complete text width + gap
duration: Math.max(4000, getTitle().length * 120)
loops: Animation.Infinite
easing.type: Easing.Linear
@@ -339,15 +343,15 @@ Item {
Item {
id: verticalLayout
anchors.centerIn: parent
- width: parent.width - Style.marginM * scaling * 2
- height: parent.height - Style.marginM * scaling * 2
- visible: barPosition === "left" || barPosition === "right"
+ width: parent.width - Style.marginM * 2
+ height: parent.height - Style.marginM * 2
+ visible: isVerticalBar
z: 1 // Above the visualizer
// Media icon
Item {
- width: Style.baseWidgetSize * 0.5 * scaling
- height: Style.baseWidgetSize * 0.5 * scaling
+ width: Style.baseWidgetSize * 0.5
+ height: width
anchors.centerIn: parent
NIcon {
@@ -355,7 +359,7 @@ Item {
anchors.fill: parent
icon: hasActivePlayer ? (MediaService.isPlaying ? "media-pause" : "media-play") : "disc"
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
@@ -387,7 +391,7 @@ Item {
onEntered: {
var textToShow = hasActivePlayer ? tooltipText : placeholderText
- if ((textToShow !== "") && (barPosition === "left" || barPosition === "right") || (scrollingMode === "never")) {
+ if ((textToShow !== "") && isVerticalBar || (scrollingMode === "never")) {
TooltipService.show(Screen, root, textToShow, BarService.getTooltipDirection())
}
}
diff --git a/Modules/Bar/Widgets/Microphone.qml b/Modules/Bar/Widgets/Microphone.qml
index d0d35727..a3058e36 100644
--- a/Modules/Bar/Widgets/Microphone.qml
+++ b/Modules/Bar/Widgets/Microphone.qml
@@ -12,7 +12,6 @@ Item {
id: root
property ShellScreen screen
- property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
@@ -92,7 +91,7 @@ Item {
rightOpen: BarService.getPillDirection(root)
icon: getIcon()
- compact: (Settings.data.bar.density === "compact")
+ density: Settings.data.bar.density
autoHide: false // Important to be false so we can hover as long as we want
text: Math.round(AudioService.inputVolume * 100)
suffix: "%"
diff --git a/Modules/Bar/Widgets/NightLight.qml b/Modules/Bar/Widgets/NightLight.qml
index f5ac5770..80d2c01d 100644
--- a/Modules/Bar/Widgets/NightLight.qml
+++ b/Modules/Bar/Widgets/NightLight.qml
@@ -11,10 +11,11 @@ import qs.Widgets
NIconButton {
id: root
- property real scaling: 1.0
+ property ShellScreen screen
- compact: (Settings.data.bar.density === "compact")
+ density: Settings.data.bar.density
baseSize: Style.capsuleHeight
+ applyUiScale: false
colorBg: Settings.data.nightLight.forced ? Color.mPrimary : (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: Settings.data.nightLight.forced ? Color.mOnPrimary : Color.mOnSurface
colorBorder: Color.transparent
diff --git a/Modules/Bar/Widgets/NotificationHistory.qml b/Modules/Bar/Widgets/NotificationHistory.qml
index ad9bb86b..6d6652fe 100644
--- a/Modules/Bar/Widgets/NotificationHistory.qml
+++ b/Modules/Bar/Widgets/NotificationHistory.qml
@@ -10,7 +10,7 @@ import qs.Widgets
NIconButton {
id: root
- property real scaling: 1.0
+ property ShellScreen screen
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
@@ -49,7 +49,8 @@ NIconButton {
}
baseSize: Style.capsuleHeight
- compact: (Settings.data.bar.density === "compact")
+ applyUiScale: false
+ density: Settings.data.bar.density
icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell"
tooltipText: Settings.data.notifications.doNotDisturb ? I18n.tr("tooltips.open-notification-history-disable-dnd") : I18n.tr("tooltips.open-notification-history-enable-dnd")
tooltipDirection: BarService.getTooltipDirection()
@@ -68,14 +69,14 @@ NIconButton {
Loader {
anchors.right: parent.right
anchors.top: parent.top
- anchors.rightMargin: 2 * scaling
- anchors.topMargin: 1 * scaling
+ anchors.rightMargin: 2
+ anchors.topMargin: 1
z: 2
active: showUnreadBadge && (!hideWhenZero || computeUnreadCount() > 0)
sourceComponent: Rectangle {
id: badge
readonly property int count: computeUnreadCount()
- height: 8 * scaling
+ height: 8
width: height
radius: height / 2
color: Color.mError
diff --git a/Modules/Bar/Widgets/PowerProfile.qml b/Modules/Bar/Widgets/PowerProfile.qml
index a89c4e7a..da96fcf1 100644
--- a/Modules/Bar/Widgets/PowerProfile.qml
+++ b/Modules/Bar/Widgets/PowerProfile.qml
@@ -9,17 +9,17 @@ import qs.Widgets
NIconButton {
id: root
- property real scaling: 1.0
+ property ShellScreen screen
baseSize: Style.capsuleHeight
+ applyUiScale: false
+ density: Settings.data.bar.density
visible: PowerProfileService.available
-
icon: PowerProfileService.getIcon()
tooltipText: I18n.tr("tooltips.power-profile", {
"profile": PowerProfileService.getName()
})
tooltipDirection: BarService.getTooltipDirection()
- compact: (Settings.data.bar.density === "compact")
colorBg: (PowerProfileService.profile === PowerProfile.Balanced) ? (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent) : Color.mPrimary
colorFg: (PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnSurface : Color.mOnPrimary
colorBorder: Color.transparent
diff --git a/Modules/Bar/Widgets/ScreenRecorder.qml b/Modules/Bar/Widgets/ScreenRecorder.qml
index 0f2dd5fa..4e528c2f 100644
--- a/Modules/Bar/Widgets/ScreenRecorder.qml
+++ b/Modules/Bar/Widgets/ScreenRecorder.qml
@@ -7,13 +7,14 @@ import qs.Widgets
NIconButton {
id: root
- property real scaling: 1.0
+ property ShellScreen screen
icon: "camera-video"
tooltipText: ScreenRecorderService.isRecording ? I18n.tr("tooltips.click-to-stop-recording") : I18n.tr("tooltips.click-to-start-recording")
tooltipDirection: BarService.getTooltipDirection()
- compact: (Settings.data.bar.density === "compact")
+ density: Settings.data.bar.density
baseSize: Style.capsuleHeight
+ applyUiScale: false
colorBg: ScreenRecorderService.isRecording ? Color.mPrimary : (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: ScreenRecorderService.isRecording ? Color.mOnPrimary : Color.mOnSurface
colorBorder: Color.transparent
diff --git a/Modules/Bar/Widgets/SessionMenu.qml b/Modules/Bar/Widgets/SessionMenu.qml
index cb506ad3..bb24f446 100644
--- a/Modules/Bar/Widgets/SessionMenu.qml
+++ b/Modules/Bar/Widgets/SessionMenu.qml
@@ -8,10 +8,11 @@ import qs.Widgets
NIconButton {
id: root
- property real scaling: 1.0
+ property ShellScreen screen
- compact: (Settings.data.bar.density === "compact")
+ density: Settings.data.bar.density
baseSize: Style.capsuleHeight
+ applyUiScale: false
icon: "power"
tooltipText: I18n.tr("tooltips.session-menu")
tooltipDirection: BarService.getTooltipDirection()
diff --git a/Modules/Bar/Widgets/Spacer.qml b/Modules/Bar/Widgets/Spacer.qml
index 8f1378e3..3af0e792 100644
--- a/Modules/Bar/Widgets/Spacer.qml
+++ b/Modules/Bar/Widgets/Spacer.qml
@@ -8,9 +8,7 @@ import qs.Widgets
Item {
id: root
- // Widget properties passed from Bar.qml
- property var screen
- property real scaling: 1.0
+ property ShellScreen screen
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
@@ -33,8 +31,8 @@ Item {
readonly property int spacerWidth: widgetSettings.width !== undefined ? widgetSettings.width : widgetMetadata.width
// Set the width based on user settings
- implicitWidth: spacerWidth * scaling
- implicitHeight: Style.barHeight * scaling
+ implicitWidth: spacerWidth
+ implicitHeight: Style.barHeight
width: implicitWidth
height: implicitHeight
}
diff --git a/Modules/Bar/Widgets/SystemMonitor.qml b/Modules/Bar/Widgets/SystemMonitor.qml
index cda66b11..7609f3bf 100644
--- a/Modules/Bar/Widgets/SystemMonitor.qml
+++ b/Modules/Bar/Widgets/SystemMonitor.qml
@@ -9,7 +9,6 @@ Rectangle {
id: root
property ShellScreen screen
- property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
@@ -30,7 +29,7 @@ Rectangle {
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
- readonly property bool compact: (Settings.data.bar.density === "compact")
+ readonly property bool density: Settings.data.bar.density
readonly property bool showCpuUsage: (widgetSettings.showCpuUsage !== undefined) ? widgetSettings.showCpuUsage : widgetMetadata.showCpuUsage
readonly property bool showCpuTemp: (widgetSettings.showCpuTemp !== undefined) ? widgetSettings.showCpuTemp : widgetMetadata.showCpuTemp
@@ -42,12 +41,12 @@ Rectangle {
readonly property real iconSize: textSize * 1.4
readonly property real textSize: {
var base = isVertical ? width * 0.82 : height
- return Math.max(1, compact ? base * 0.43 : base * 0.33)
+ return Math.max(1, (density === "compact") ? base * 0.43 : base * 0.33)
}
- readonly property int percentTextWidth: Math.ceil(percentMetrics.boundingRect.width + 3 * scaling)
- readonly property int tempTextWidth: Math.ceil(tempMetrics.boundingRect.width + 3 * scaling)
- readonly property int memTextWidth: Math.ceil(memMetrics.boundingRect.width + 3 * scaling)
+ readonly property int percentTextWidth: Math.ceil(percentMetrics.boundingRect.width + 3)
+ readonly property int tempTextWidth: Math.ceil(tempMetrics.boundingRect.width + 3)
+ readonly property int memTextWidth: Math.ceil(memMetrics.boundingRect.width + 3)
TextMetrics {
id: percentMetrics
@@ -74,9 +73,9 @@ Rectangle {
}
anchors.centerIn: parent
- implicitWidth: isVertical ? Math.round(Style.capsuleHeight * scaling) : Math.round(mainGrid.implicitWidth + Style.marginM * 2 * scaling)
- implicitHeight: isVertical ? Math.round(mainGrid.implicitHeight + Style.marginM * 2 * scaling) : Math.round(Style.capsuleHeight * scaling)
- radius: Math.round(Style.radiusM * scaling)
+ implicitWidth: isVertical ? Style.capsuleHeight : Math.round(mainGrid.implicitWidth + Style.marginM * 2)
+ implicitHeight: isVertical ? Math.round(mainGrid.implicitHeight + Style.marginM * 2) : Style.capsuleHeight
+ radius: Style.radiusM
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
GridLayout {
@@ -85,13 +84,13 @@ Rectangle {
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? -1 : 1
columns: isVertical ? 1 : -1
- rowSpacing: isVertical ? (Style.marginM * scaling) : 0
- columnSpacing: isVertical ? 0 : (Style.marginM * scaling)
+ rowSpacing: isVertical ? (Style.marginM) : 0
+ columnSpacing: isVertical ? 0 : (Style.marginM)
// CPU Usage Component
Item {
- Layout.preferredWidth: isVertical ? root.width : iconSize + percentTextWidth + (Style.marginXXS * scaling)
- Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
+ Layout.preferredWidth: isVertical ? root.width : iconSize + percentTextWidth + (Style.marginXXS)
+ Layout.preferredHeight: Style.capsuleHeight
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showCpuUsage
@@ -101,12 +100,13 @@ Rectangle {
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
- rowSpacing: Style.marginXXS * scaling
- columnSpacing: Style.marginXXS * scaling
+ rowSpacing: Style.marginXXS
+ columnSpacing: Style.marginXXS
NIcon {
icon: "cpu-usage"
pointSize: iconSize
+ applyUiScale: false
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
@@ -116,6 +116,7 @@ Rectangle {
text: `${Math.round(SystemStatService.cpuUsage)}%`
family: Settings.data.ui.fontFixed
pointSize: textSize
+ applyUiScale: false
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : percentTextWidth
@@ -131,8 +132,8 @@ Rectangle {
// CPU Temperature Component
Item {
- Layout.preferredWidth: isVertical ? root.width : (iconSize + tempTextWidth) + (Style.marginXXS * scaling)
- Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
+ Layout.preferredWidth: isVertical ? root.width : (iconSize + tempTextWidth) + (Style.marginXXS)
+ Layout.preferredHeight: Style.capsuleHeight
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showCpuTemp
@@ -142,12 +143,13 @@ Rectangle {
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
- rowSpacing: Style.marginXXS * scaling
- columnSpacing: Style.marginXXS * scaling
+ rowSpacing: Style.marginXXS
+ columnSpacing: Style.marginXXS
NIcon {
icon: "cpu-temperature"
pointSize: iconSize
+ applyUiScale: false
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
@@ -157,6 +159,7 @@ Rectangle {
text: `${Math.round(SystemStatService.cpuTemp)}°`
family: Settings.data.ui.fontFixed
pointSize: textSize
+ applyUiScale: false
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : tempTextWidth
@@ -172,8 +175,8 @@ Rectangle {
// Memory Usage Component
Item {
- Layout.preferredWidth: isVertical ? root.width : iconSize + (showMemoryAsPercent ? percentTextWidth : memTextWidth) + (Style.marginXXS * scaling)
- Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
+ Layout.preferredWidth: isVertical ? root.width : iconSize + (showMemoryAsPercent ? percentTextWidth : memTextWidth) + (Style.marginXXS)
+ Layout.preferredHeight: Style.capsuleHeight
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showMemoryUsage
@@ -183,12 +186,13 @@ Rectangle {
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
- rowSpacing: Style.marginXXS * scaling
- columnSpacing: Style.marginXXS * scaling
+ rowSpacing: Style.marginXXS
+ columnSpacing: Style.marginXXS
NIcon {
icon: "memory"
pointSize: iconSize
+ applyUiScale: false
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
@@ -198,6 +202,7 @@ Rectangle {
text: showMemoryAsPercent ? `${Math.round(SystemStatService.memPercent)}%` : `${SystemStatService.memGb.toFixed(1)}G`
family: Settings.data.ui.fontFixed
pointSize: textSize
+ applyUiScale: false
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : (showMemoryAsPercent ? percentTextWidth : memTextWidth)
@@ -213,8 +218,8 @@ Rectangle {
// Network Download Speed Component
Item {
- Layout.preferredWidth: isVertical ? root.width : iconSize + memTextWidth + (Style.marginXXS * scaling)
- Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
+ Layout.preferredWidth: isVertical ? root.width : iconSize + memTextWidth + (Style.marginXXS)
+ Layout.preferredHeight: Style.capsuleHeight
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showNetworkStats
@@ -224,12 +229,13 @@ Rectangle {
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
- rowSpacing: Style.marginXXS * scaling
- columnSpacing: Style.marginXXS * scaling
+ rowSpacing: Style.marginXXS
+ columnSpacing: Style.marginXXS
NIcon {
icon: "download-speed"
pointSize: iconSize
+ applyUiScale: false
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
@@ -239,6 +245,7 @@ Rectangle {
text: isVertical ? SystemStatService.formatCompactSpeed(SystemStatService.rxSpeed) : SystemStatService.formatSpeed(SystemStatService.rxSpeed)
family: Settings.data.ui.fontFixed
pointSize: textSize
+ applyUiScale: false
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : memTextWidth
@@ -254,8 +261,8 @@ Rectangle {
// Network Upload Speed Component
Item {
- Layout.preferredWidth: isVertical ? root.width : iconSize + memTextWidth + (Style.marginXXS * scaling)
- Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
+ Layout.preferredWidth: isVertical ? root.width : iconSize + memTextWidth + (Style.marginXXS)
+ Layout.preferredHeight: Style.capsuleHeight
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showNetworkStats
@@ -265,12 +272,13 @@ Rectangle {
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
- rowSpacing: Style.marginXXS * scaling
- columnSpacing: Style.marginXXS * scaling
+ rowSpacing: Style.marginXXS
+ columnSpacing: Style.marginXXS
NIcon {
icon: "upload-speed"
pointSize: iconSize
+ applyUiScale: false
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
@@ -280,6 +288,7 @@ Rectangle {
text: isVertical ? SystemStatService.formatCompactSpeed(SystemStatService.txSpeed) : SystemStatService.formatSpeed(SystemStatService.txSpeed)
family: Settings.data.ui.fontFixed
pointSize: textSize
+ applyUiScale: false
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : memTextWidth
@@ -295,8 +304,8 @@ Rectangle {
// Disk Usage Component (primary drive)
Item {
- Layout.preferredWidth: isVertical ? root.width : iconSize + percentTextWidth + (Style.marginXXS * scaling)
- Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
+ Layout.preferredWidth: isVertical ? root.width : iconSize + percentTextWidth + (Style.marginXXS)
+ Layout.preferredHeight: Style.capsuleHeight
Layout.alignment: isVertical ? Qt.AlignHCenter : Qt.AlignVCenter
visible: showDiskUsage
@@ -306,12 +315,13 @@ Rectangle {
flow: isVertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rows: isVertical ? 2 : 1
columns: isVertical ? 1 : 2
- rowSpacing: Style.marginXXS * scaling
- columnSpacing: Style.marginXXS * scaling
+ rowSpacing: Style.marginXXS
+ columnSpacing: Style.marginXXS
NIcon {
icon: "storage"
pointSize: iconSize
+ applyUiScale: false
Layout.alignment: Qt.AlignCenter
Layout.row: isVertical ? 1 : 0
Layout.column: 0
@@ -321,6 +331,7 @@ Rectangle {
text: `${SystemStatService.diskPercent}%`
family: Settings.data.ui.fontFixed
pointSize: textSize
+ applyUiScale: false
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: isVertical ? -1 : percentTextWidth
diff --git a/Modules/Bar/Widgets/Taskbar.qml b/Modules/Bar/Widgets/Taskbar.qml
index 9a0b356e..8dcab751 100644
--- a/Modules/Bar/Widgets/Taskbar.qml
+++ b/Modules/Bar/Widgets/Taskbar.qml
@@ -12,7 +12,6 @@ Rectangle {
id: root
property ShellScreen screen
- property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
@@ -21,8 +20,8 @@ Rectangle {
property int sectionWidgetsCount: 0
readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
- readonly property bool compact: (Settings.data.bar.density === "compact")
- readonly property real itemSize: compact ? Style.capsuleHeight * 0.9 * scaling : Style.capsuleHeight * 0.8 * scaling
+ readonly property bool 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]
property var widgetSettings: {
@@ -36,27 +35,27 @@ Rectangle {
}
// Always visible when there are toplevels
- implicitWidth: isVerticalBar ? Math.round(Style.capsuleHeight * scaling) : taskbarLayout.implicitWidth + Style.marginM * scaling * 2
- implicitHeight: isVerticalBar ? taskbarLayout.implicitHeight + Style.marginM * scaling * 2 : Math.round(Style.capsuleHeight * scaling)
- radius: Math.round(Style.radiusM * scaling)
+ implicitWidth: isVerticalBar ? Style.capsuleHeight : Math.round(taskbarLayout.implicitWidth + Style.marginM * 2)
+ implicitHeight: isVerticalBar ? Math.round(taskbarLayout.implicitHeight + Style.marginM * 2) : Style.capsuleHeight
+ radius: Style.radiusM
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
GridLayout {
id: taskbarLayout
anchors.fill: parent
anchors {
- leftMargin: isVerticalBar ? undefined : Style.marginM * scaling
- rightMargin: isVerticalBar ? undefined : Style.marginM * scaling
- topMargin: compact ? 0 : isVerticalBar ? Style.marginM * scaling : undefined
- bottomMargin: compact ? 0 : isVerticalBar ? Style.marginM * scaling : undefined
+ leftMargin: isVerticalBar ? undefined : Style.marginM
+ rightMargin: isVerticalBar ? undefined : Style.marginM
+ topMargin: (density === "compact") ? 0 : isVerticalBar ? Style.marginM : undefined
+ bottomMargin: (density === "compact") ? 0 : isVerticalBar ? Style.marginM : undefined
}
// Configure GridLayout to behave like RowLayout or ColumnLayout
rows: isVerticalBar ? -1 : 1 // -1 means unlimited
columns: isVerticalBar ? 1 : -1 // -1 means unlimited
- rowSpacing: isVerticalBar ? Style.marginXXS * root.scaling : 0
- columnSpacing: isVerticalBar ? 0 : Style.marginXXS * root.scaling
+ rowSpacing: isVerticalBar ? Style.marginXXS : 0
+ columnSpacing: isVerticalBar ? 0 : Style.marginXXS
Repeater {
model: CompositorService.windows
@@ -81,13 +80,22 @@ Rectangle {
asynchronous: true
opacity: modelData.isFocused ? Style.opacityFull : 0.6
+ // Apply dock shader to all taskbar icons
+ layer.enabled: widgetSettings.colorizeIcons !== false
+ layer.effect: ShaderEffect {
+ property color targetColor: Color.mOnSurface
+ property real colorizeMode: 0.0 // Dock mode (grayscale)
+
+ fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb")
+ }
+
Rectangle {
- anchors.bottomMargin: -2 * scaling
+ anchors.bottomMargin: -2
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
id: iconBackground
- width: 4 * scaling
- height: 4 * scaling
+ width: 4
+ height: 4
color: modelData.isFocused ? Color.mPrimary : Color.transparent
radius: width * 0.5
}
@@ -105,13 +113,13 @@ Rectangle {
if (mouse.button === Qt.LeftButton) {
try {
- CompositorService.focusWindow(taskbarItem.modelData.id)
+ CompositorService.focusWindow(taskbarItem.modelData)
} catch (error) {
Logger.error("Taskbar", "Failed to activate toplevel: " + error)
}
} else if (mouse.button === Qt.RightButton) {
try {
- CompositorService.closeWindow(taskbarItem.modelData.id)
+ CompositorService.closeWindow(taskbarItem.modelData)
} catch (error) {
Logger.error("Taskbar", "Failed to close toplevel: " + error)
}
diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml
index b93c4b7a..f76be377 100644
--- a/Modules/Bar/Widgets/Tray.qml
+++ b/Modules/Bar/Widgets/Tray.qml
@@ -14,12 +14,109 @@ Rectangle {
id: root
property ShellScreen screen
- property real scaling: 1.0
+
+ // Widget properties passed from Bar.qml for per-instance settings
+ property string widgetId: ""
+ property string section: ""
+ property int sectionWidgetIndex: -1
+ property int sectionWidgetsCount: 0
+
+ property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
+ property var widgetSettings: {
+ if (section && sectionWidgetIndex >= 0) {
+ var widgets = Settings.data.bar.widgets[section]
+ if (widgets && sectionWidgetIndex < widgets.length) {
+ return widgets[sectionWidgetIndex]
+ }
+ }
+ return {}
+ }
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
- readonly property bool compact: (Settings.data.bar.density === "compact")
- readonly property real itemSize: isVertical ? width * 0.75 : height * 0.85
+ readonly property bool density: Settings.data.bar.density
+ property real itemSize: Math.round(Style.capsuleHeight * 0.65)
+ property list blacklist: widgetSettings.blacklist || widgetMetadata.blacklist || [] // Read from settings
+ property var filteredItems: []
+
+ function wildCardMatch(str, rule) {
+ if (!str || !rule) {
+ return false
+ }
+ Logger.log("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, '.*')
+ // Add ^ and $ to match the entire string
+ pattern = '^' + pattern + '$'
+
+ Logger.log("Tray", "wildCardMatch - Generated pattern:", pattern)
+
+ try {
+ const regex = new RegExp(pattern, 'i')
+ // 'i' for case-insensitive
+ Logger.log("Tray", "wildCardMatch - Regex test result:", regex.test(str))
+ return regex.test(str)
+ } catch (e) {
+ Logger.warn("Tray", "Invalid regex pattern for wildcard match:", rule, e.message)
+ return false // If regex is invalid, it won't match
+ }
+ }
+
+ // Debounce timer for updateFilteredItems to prevent excessive calls
+ // when multiple events (e.g., SystemTray changes, settings saves)
+ // trigger it in rapid succession, reducing redundant processing.
+ Timer {
+ id: updateDebounceTimer
+ interval: 100 // milliseconds
+ running: false
+ repeat: false
+ onTriggered: _performFilteredItemsUpdate()
+ }
+
+ function _performFilteredItemsUpdate() {
+ if (!root.blacklist || root.blacklist.length === 0) {
+ if (SystemTray.items && SystemTray.items.values) {
+ filteredItems = SystemTray.items.values
+ } else {
+ filteredItems = []
+ }
+ return
+ }
+
+ let newItems = []
+ if (SystemTray.items && SystemTray.items.values) {
+ const trayItems = SystemTray.items.values
+ for (var i = 0; i < trayItems.length; i++) {
+ const item = trayItems[i]
+ if (!item) {
+ continue
+ }
+
+ const title = item.tooltipTitle || item.name || item.id || ""
+
+ let isBlacklisted = false
+ for (var j = 0; j < root.blacklist.length; j++) {
+ const rule = root.blacklist[j]
+ if (wildCardMatch(title, rule)) {
+ isBlacklisted = true
+ break
+ }
+ }
+
+ if (!isBlacklisted) {
+ newItems.push(item)
+ }
+ }
+ }
+ filteredItems = newItems
+ }
+
+ function updateFilteredItems() {
+ updateDebounceTimer.restart()
+ }
function onLoaded() {
// When the widget is fully initialized with its props set the screen for the trayMenu
@@ -28,10 +125,28 @@ Rectangle {
}
}
- visible: SystemTray.items.values.length > 0
- implicitWidth: isVertical ? Math.round(Style.capsuleHeight * scaling) : (trayFlow.implicitWidth + Style.marginS * scaling * 2)
- implicitHeight: isVertical ? (trayFlow.implicitHeight + Style.marginS * scaling * 2) : Math.round(Style.capsuleHeight * scaling)
- radius: Math.round(Style.radiusM * scaling)
+ Connections {
+ target: SystemTray.items
+ function onValuesChanged() {
+ root.updateFilteredItems()
+ }
+ }
+
+ Connections {
+ target: Settings
+ function onSettingsSaved() {
+ root.updateFilteredItems()
+ }
+ }
+
+ Component.onCompleted: {
+ root.updateFilteredItems() // Initial update
+ }
+
+ visible: filteredItems.length > 0
+ implicitWidth: isVertical ? Style.capsuleHeight : Math.round(trayFlow.implicitWidth + Style.marginM * 2)
+ implicitHeight: isVertical ? Math.round(trayFlow.implicitHeight + Style.marginM * 2) : Style.capsuleHeight
+ radius: Style.radiusM
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
Layout.alignment: Qt.AlignVCenter
@@ -39,12 +154,12 @@ Rectangle {
Flow {
id: trayFlow
anchors.centerIn: parent
- spacing: Style.marginS * scaling
+ spacing: Style.marginM
flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight
Repeater {
id: repeater
- model: SystemTray.items
+ model: filteredItems
delegate: Item {
width: itemSize
@@ -56,10 +171,7 @@ Rectangle {
property ShellScreen screen: root.screen
- anchors.centerIn: parent
- width: Style.marginL * scaling
- height: Style.marginL * scaling
- smooth: false
+ anchors.fill: parent
asynchronous: true
backer.fillMode: Image.PreserveAspectFit
source: {
@@ -70,7 +182,6 @@ Rectangle {
// Process icon path
if (icon.includes("?path=")) {
- // Seems qmlfmt does not support the following ES6 syntax: const[name, path] = icon.split
const chunks = icon.split("?path=")
const name = chunks[0]
const path = chunks[1]
@@ -80,69 +191,77 @@ Rectangle {
return icon
}
opacity: status === Image.Ready ? 1 : 0
- }
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
- onClicked: mouse => {
- if (!modelData) {
- return
- }
+ layer.enabled: widgetSettings.colorizeIcons !== false
+ layer.effect: ShaderEffect {
+ property color targetColor: Color.mOnSurface
+ property real colorizeMode: 1.0 // Tray mode (intensity-based)
- if (mouse.button === Qt.LeftButton) {
- // Close any open menu first
- trayPanel.close()
+ fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb")
+ }
- if (!modelData.onlyMenu) {
- modelData.activate()
- }
- } else if (mouse.button === Qt.MiddleButton) {
- // Close any open menu first
- trayPanel.close()
-
- modelData.secondaryActivate && modelData.secondaryActivate()
- } else if (mouse.button === Qt.RightButton) {
- TooltipService.hideImmediately()
-
- // Close the menu if it was visible
- if (trayPanel && trayPanel.visible) {
- trayPanel.close()
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
+ onClicked: mouse => {
+ if (!modelData) {
return
}
- if (modelData.hasMenu && modelData.menu && trayMenu.item) {
- trayPanel.open()
+ if (mouse.button === Qt.LeftButton) {
+ // Close any open menu first
+ trayPanel.close()
- // Position menu based on bar position
- let menuX, menuY
- if (barPosition === "left") {
- // For left bar: position menu to the right of the bar
- menuX = width + Style.marginM * scaling
- menuY = 0
- } else if (barPosition === "right") {
- // For right bar: position menu to the left of the bar
- menuX = -trayMenu.item.width - Style.marginM * scaling
- menuY = 0
+ if (!modelData.onlyMenu) {
+ modelData.activate()
+ }
+ } else if (mouse.button === Qt.MiddleButton) {
+ // Close any open menu first
+ trayPanel.close()
+
+ modelData.secondaryActivate && modelData.secondaryActivate()
+ } else if (mouse.button === Qt.RightButton) {
+ TooltipService.hideImmediately()
+
+ // Close the menu if it was visible
+ if (trayPanel && trayPanel.visible) {
+ trayPanel.close()
+ return
+ }
+
+ if (modelData.hasMenu && modelData.menu && trayMenu.item) {
+ trayPanel.open()
+
+ // Position menu based on bar position
+ let menuX, menuY
+ if (barPosition === "left") {
+ // For left bar: position menu to the right of the bar
+ menuX = width + Style.marginM
+ menuY = 0
+ } else if (barPosition === "right") {
+ // For right bar: position menu to the left of the bar
+ menuX = -trayMenu.item.width - Style.marginM
+ menuY = 0
+ } else {
+ // For horizontal bars: center horizontally and position below
+ menuX = (width / 2) - (trayMenu.item.width / 2)
+ menuY = Style.barHeight
+ }
+ trayMenu.item.menu = modelData.menu
+ trayMenu.item.showAt(parent, menuX, menuY)
} else {
- // For horizontal bars: center horizontally and position below
- menuX = (width / 2) - (trayMenu.item.width / 2)
- menuY = Math.round(Style.barHeight * scaling)
+ Logger.log("Tray", "No menu available for", modelData.id, "or trayMenu not set")
}
- trayMenu.item.menu = modelData.menu
- trayMenu.item.showAt(parent, menuX, menuY)
- } else {
- Logger.log("Tray", "No menu available for", modelData.id, "or trayMenu not set")
}
}
- }
- onEntered: {
- trayPanel.close()
- TooltipService.show(Screen, trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection())
+ onEntered: {
+ trayPanel.close()
+ TooltipService.show(Screen, trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection())
+ }
+ onExited: TooltipService.hide()
}
- onExited: TooltipService.hide()
}
}
}
diff --git a/Modules/Bar/Widgets/Volume.qml b/Modules/Bar/Widgets/Volume.qml
index 775a31d6..4254b842 100644
--- a/Modules/Bar/Widgets/Volume.qml
+++ b/Modules/Bar/Widgets/Volume.qml
@@ -12,7 +12,6 @@ Item {
id: root
property ShellScreen screen
- property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
@@ -75,7 +74,7 @@ Item {
BarPill {
id: pill
- compact: (Settings.data.bar.density === "compact")
+ density: Settings.data.bar.density
rightOpen: BarService.getPillDirection(root)
icon: getIcon()
autoHide: false // Important to be false so we can hover as long as we want
diff --git a/Modules/Bar/Widgets/WallpaperSelector.qml b/Modules/Bar/Widgets/WallpaperSelector.qml
index 51188450..e48f368a 100644
--- a/Modules/Bar/Widgets/WallpaperSelector.qml
+++ b/Modules/Bar/Widgets/WallpaperSelector.qml
@@ -8,10 +8,11 @@ import qs.Widgets
NIconButton {
id: root
- property real scaling: 1.0
+ property ShellScreen screen
baseSize: Style.capsuleHeight
- compact: (Settings.data.bar.density === "compact")
+ applyUiScale: false
+ density: Settings.data.bar.density
icon: "wallpaper-selector"
tooltipText: I18n.tr("tooltips.open-wallpaper-selector")
tooltipDirection: BarService.getTooltipDirection()
diff --git a/Modules/Bar/Widgets/WiFi.qml b/Modules/Bar/Widgets/WiFi.qml
index 58a0e885..a9156e3e 100644
--- a/Modules/Bar/Widgets/WiFi.qml
+++ b/Modules/Bar/Widgets/WiFi.qml
@@ -10,10 +10,11 @@ import qs.Widgets
NIconButton {
id: root
- property real scaling: 1.0
+ property ShellScreen screen
- compact: (Settings.data.bar.density === "compact")
+ density: Settings.data.bar.density
baseSize: Style.capsuleHeight
+ applyUiScale: false
colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
colorFg: Color.mOnSurface
colorBorder: Color.transparent
diff --git a/Modules/Bar/Widgets/Workspace.qml b/Modules/Bar/Widgets/Workspace.qml
index 689b962f..96c301f6 100644
--- a/Modules/Bar/Widgets/Workspace.qml
+++ b/Modules/Bar/Widgets/Workspace.qml
@@ -13,7 +13,6 @@ Item {
id: root
property ShellScreen screen
- property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
@@ -34,9 +33,9 @@ Item {
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
- readonly property bool compact: (Settings.data.bar.density === "compact")
+ readonly property bool density: Settings.data.bar.density
readonly property real baseDimensionRatio: {
- const b = compact ? 0.85 : 0.65
+ const b = (density === "compact") ? 0.85 : 0.65
if (widgetSettings.labelMode === "none") {
return b * 0.75
}
@@ -54,8 +53,8 @@ Item {
property bool effectsActive: false
property color effectColor: Color.mPrimary
- property int horizontalPadding: Math.round(Style.marginS * scaling)
- property int spacingBetweenPills: Math.round(Style.marginXS * scaling)
+ property int horizontalPadding: Math.round(Style.marginS)
+ property int spacingBetweenPills: Math.round(Style.marginXS)
// Wheel scroll handling
property int wheelAccumulatedDelta: 0
@@ -63,19 +62,19 @@ Item {
signal workspaceChanged(int workspaceId, color accentColor)
- implicitWidth: isVertical ? Math.round(Style.barHeight * scaling) : computeWidth()
- implicitHeight: isVertical ? computeHeight() : Math.round(Style.barHeight * scaling)
+ implicitWidth: isVertical ? Style.barHeight : computeWidth()
+ implicitHeight: isVertical ? computeHeight() : Style.barHeight
function getWorkspaceWidth(ws) {
const d = Style.capsuleHeight * root.baseDimensionRatio
- const factor = ws.isFocused ? 2.2 : 1
- return d * factor * scaling
+ const factor = ws.isActive ? 2.2 : 1
+ return d * factor
}
function getWorkspaceHeight(ws) {
const d = Style.capsuleHeight * root.baseDimensionRatio
- const factor = ws.isFocused ? 2.2 : 1
- return d * factor * scaling
+ const factor = ws.isActive ? 2.2 : 1
+ return d * factor
}
function computeWidth() {
@@ -119,7 +118,7 @@ Item {
next = localWorkspaces.count - 1
const ws = localWorkspaces.get(next)
if (ws && ws.idx !== undefined)
- CompositorService.switchToWorkspace(ws.idx)
+ CompositorService.switchToWorkspace(ws)
}
Component.onCompleted: {
@@ -203,9 +202,9 @@ Item {
Rectangle {
id: workspaceBackground
- width: isVertical ? Math.round(Style.capsuleHeight * scaling) : parent.width
- height: isVertical ? parent.height : Math.round(Style.capsuleHeight * scaling)
- radius: Math.round(Style.radiusM * scaling)
+ width: isVertical ? Style.capsuleHeight : parent.width
+ height: isVertical ? parent.height : Style.capsuleHeight
+ radius: Style.radiusM
color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent
anchors.horizontalCenter: parent.horizontalCenter
@@ -266,7 +265,7 @@ Item {
Item {
id: workspacePillContainer
width: root.getWorkspaceWidth(model)
- height: Style.capsuleHeight * root.baseDimensionRatio * scaling
+ height: Style.capsuleHeight * root.baseDimensionRatio
Rectangle {
id: pill
@@ -286,7 +285,8 @@ Item {
}
}
family: Settings.data.ui.fontFixed
- pointSize: model.isFocused ? workspacePillContainer.height * 0.45 : workspacePillContainer.height * 0.42
+ pointSize: model.isActive ? workspacePillContainer.height * 0.45 : workspacePillContainer.height * 0.42
+ applyUiScale: false
font.capitalization: Font.AllUppercase
font.weight: Style.fontWeightBold
wrapMode: Text.Wrap
@@ -295,7 +295,7 @@ Item {
return Color.mOnPrimary
if (model.isUrgent)
return Color.mOnError
- if (model.isActive || model.isOccupied)
+ if (model.isOccupied)
return Color.mOnSecondary
return Color.mOnSecondary
@@ -310,12 +310,12 @@ Item {
return Color.mPrimary
if (model.isUrgent)
return Color.mError
- if (model.isActive || model.isOccupied)
+ if (model.isOccupied)
return Color.mSecondary
return Qt.alpha(Color.mSecondary, 0.3)
}
- scale: model.isFocused ? 1.0 : 0.9
+ scale: model.isActive ? 1.0 : 0.9
z: 0
MouseArea {
@@ -323,7 +323,7 @@ Item {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
- CompositorService.switchToWorkspace(model.idx)
+ CompositorService.switchToWorkspace(model)
}
hoverEnabled: true
}
@@ -387,7 +387,7 @@ Item {
radius: width / 2
color: Color.transparent
border.color: root.effectColor
- border.width: Math.max(1, Math.round((2 + 6 * (1.0 - root.masterProgress)) * scaling))
+ border.width: Math.max(1, Math.round((2 + 6 * (1.0 - root.masterProgress))))
opacity: root.effectsActive && model.isFocused ? (1.0 - root.masterProgress) * 0.7 : 0
visible: root.effectsActive && model.isFocused
z: 1
@@ -409,7 +409,7 @@ Item {
model: localWorkspaces
Item {
id: workspacePillContainerVertical
- width: Style.capsuleHeight * root.baseDimensionRatio * scaling
+ width: Style.capsuleHeight * root.baseDimensionRatio
height: root.getWorkspaceHeight(model)
Rectangle {
@@ -430,7 +430,8 @@ Item {
}
}
family: Settings.data.ui.fontFixed
- pointSize: model.isFocused ? workspacePillContainerVertical.width * 0.45 : workspacePillContainerVertical.width * 0.42
+ pointSize: model.isActive ? workspacePillContainerVertical.width * 0.45 : workspacePillContainerVertical.width * 0.42
+ applyUiScale: false
font.capitalization: Font.AllUppercase
font.weight: Style.fontWeightBold
wrapMode: Text.Wrap
@@ -439,7 +440,7 @@ Item {
return Color.mOnPrimary
if (model.isUrgent)
return Color.mOnError
- if (model.isActive || model.isOccupied)
+ if (model.isOccupied)
return Color.mOnSecondary
return Color.mOnSurface
@@ -454,12 +455,12 @@ Item {
return Color.mPrimary
if (model.isUrgent)
return Color.mError
- if (model.isActive || model.isOccupied)
+ if (model.isOccupied)
return Color.mSecondary
return Color.mOutline
}
- scale: model.isFocused ? 1.0 : 0.9
+ scale: model.isActive ? 1.0 : 0.9
z: 0
MouseArea {
@@ -467,7 +468,7 @@ Item {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
- CompositorService.switchToWorkspace(model.idx)
+ CompositorService.switchToWorkspace(model)
}
hoverEnabled: true
}
@@ -531,7 +532,7 @@ Item {
radius: width / 2
color: Color.transparent
border.color: root.effectColor
- border.width: Math.max(1, Math.round((2 + 6 * (1.0 - root.masterProgress)) * scaling))
+ border.width: Math.max(1, Math.round((2 + 6 * (1.0 - root.masterProgress))))
opacity: root.effectsActive && model.isFocused ? (1.0 - root.masterProgress) * 0.7 : 0
visible: root.effectsActive && model.isFocused
z: 1
diff --git a/Modules/ControlCenter/Cards/AudioCard.qml b/Modules/ControlCenter/Cards/AudioCard.qml
new file mode 100644
index 00000000..7996ce4b
--- /dev/null
+++ b/Modules/ControlCenter/Cards/AudioCard.qml
@@ -0,0 +1,155 @@
+import QtQuick
+import QtQuick.Layouts
+import Quickshell
+import qs.Commons
+import qs.Services
+import qs.Widgets
+
+// Audio controls card: output and input volume controls
+NBox {
+ id: root
+
+ property real localOutputVolume: AudioService.volume || 0
+ property bool localOutputVolumeChanging: false
+
+ property real localInputVolume: AudioService.inputVolume || 0
+ property bool localInputVolumeChanging: false
+
+ // Timer to debounce volume changes
+ Timer {
+ interval: 100
+ running: true
+ repeat: true
+ onTriggered: {
+ if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) {
+ AudioService.setVolume(localOutputVolume)
+ }
+ if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) {
+ AudioService.setInputVolume(localInputVolume)
+ }
+ }
+ }
+
+ // Connections to update local volumes when AudioService changes
+ Connections {
+ target: AudioService.sink?.audio ? AudioService.sink?.audio : null
+ function onVolumeChanged() {
+ if (!localOutputVolumeChanging) {
+ localOutputVolume = AudioService.volume
+ }
+ }
+ }
+
+ Connections {
+ target: AudioService.source?.audio ? AudioService.source?.audio : null
+ function onVolumeChanged() {
+ if (!localInputVolumeChanging) {
+ localInputVolume = AudioService.inputVolume
+ }
+ }
+ }
+
+ RowLayout {
+ anchors.fill: parent
+ anchors.margins: Style.marginM
+ spacing: Style.marginM
+
+ // Output Volume Section
+ ColumnLayout {
+ spacing: Style.marginXXS
+ Layout.fillWidth: true
+ opacity: AudioService.sink ? 1.0 : 0.5
+ enabled: AudioService.sink
+
+ // Output Volume Header
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: Style.marginXS
+
+ NIconButton {
+ icon: AudioService.muted ? "volume-off" : "volume-high"
+ baseSize: Style.baseWidgetSize * 0.5
+ colorFg: AudioService.muted ? Color.mError : Color.mOnSurface
+ colorBg: Color.transparent
+ colorBgHover: Color.mTertiary
+ colorFgHover: Color.mOnTertiary
+ onClicked: {
+ if (AudioService.sink && AudioService.sink.audio) {
+ AudioService.sink.audio.muted = !AudioService.muted
+ }
+ }
+ }
+
+ NText {
+ text: AudioService.sink ? AudioService.sink.description : "No output device"
+ pointSize: Style.fontSizeXS
+ color: Color.mOnSurfaceVariant
+ font.weight: Style.fontWeightMedium
+ elide: Text.ElideRight
+ Layout.fillWidth: true
+ }
+ }
+
+ // Output Volume Slider
+ NSlider {
+ Layout.fillWidth: true
+ from: 0
+ to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0
+ value: localOutputVolume
+ stepSize: 0.01
+ heightRatio: 0.5
+ onMoved: localOutputVolume = value
+ onPressedChanged: localOutputVolumeChanging = pressed
+ tooltipText: `${Math.round(localOutputVolume * 100)}%`
+ tooltipDirection: "bottom"
+ }
+ }
+
+ // Input Volume Section
+ ColumnLayout {
+ spacing: Style.marginXXS
+ Layout.fillWidth: true
+ opacity: AudioService.source ? 1.0 : 0.5
+ enabled: AudioService.source
+
+ // Input Volume Header
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: Style.marginXS
+
+ NIconButton {
+ icon: AudioService.inputMuted ? "microphone-off" : "microphone"
+ baseSize: Style.baseWidgetSize * 0.5
+ colorFg: AudioService.inputMuted ? Color.mError : Color.mOnSurface
+ colorBg: Color.transparent
+ colorBgHover: Color.mTertiary
+ colorFgHover: Color.mOnTertiary
+ onClicked: AudioService.setInputMuted(!AudioService.inputMuted)
+ }
+
+ NText {
+ text: AudioService.source ? AudioService.source.description : "No input device"
+ pointSize: Style.fontSizeXS
+ color: Color.mOnSurfaceVariant
+ font.weight: Style.fontWeightMedium
+ elide: Text.ElideRight
+ Layout.fillWidth: true
+ }
+ }
+
+ // Input Volume Slider
+ NSlider {
+ Layout.fillWidth: true
+ from: 0
+ to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0
+ value: localInputVolume
+ stepSize: 0.01
+ heightRatio: 0.5
+ onMoved: localInputVolume = value
+ onPressedChanged: localInputVolumeChanging = pressed
+ tooltipText: `${Math.round(localInputVolume * 100)}%`
+ tooltipDirection: "bottom"
+ }
+ }
+ }
+}
diff --git a/Modules/ControlCenter/Cards/MediaCard.qml b/Modules/ControlCenter/Cards/MediaCard.qml
index 0781b984..06891868 100644
--- a/Modules/ControlCenter/Cards/MediaCard.qml
+++ b/Modules/ControlCenter/Cards/MediaCard.qml
@@ -20,7 +20,7 @@ NBox {
id: bgArtImage
anchors.fill: parent
imagePath: MediaService.trackArtUrl
- imageRadius: Style.radiusM * scaling
+ imageRadius: Style.radiusM
visible: MediaService.trackArtUrl !== ""
}
@@ -29,7 +29,7 @@ NBox {
anchors.fill: parent
color: Color.mSurfaceVariant
opacity: 0.85
- radius: Style.radiusM * scaling
+ radius: Style.radiusM
}
// Border
@@ -38,7 +38,7 @@ NBox {
color: Color.transparent
border.color: Color.mOutline
border.width: 1
- radius: Style.radiusM * scaling
+ radius: Style.radiusM
}
}
@@ -55,7 +55,7 @@ NBox {
sourceItem: Rectangle {
width: root.width
height: root.height
- radius: Style.radiusM * scaling
+ radius: Style.radiusM
color: "white"
}
}
@@ -84,7 +84,7 @@ NBox {
anchors.fill: parent
values: CavaService.values
fillColor: Color.mPrimary
- opacity: MediaService.trackArtUrl !== "" ? 0.4 : 0.8
+ opacity: MediaService.trackArtUrl !== "" ? 0.5 : 0.8
}
}
@@ -94,7 +94,7 @@ NBox {
anchors.fill: parent
values: CavaService.values
fillColor: Color.mPrimary
- opacity: MediaService.trackArtUrl !== "" ? 0.4 : 0.8
+ opacity: MediaService.trackArtUrl !== "" ? 0.5 : 0.8
}
}
@@ -104,41 +104,41 @@ NBox {
anchors.fill: parent
values: CavaService.values
fillColor: Color.mPrimary
- opacity: MediaService.trackArtUrl !== "" ? 0.4 : 0.8
+ opacity: MediaService.trackArtUrl !== "" ? 0.5 : 0.8
}
}
}
}
- // Player selector - positioned at the very top
+ // Player selector
Rectangle {
id: playerSelectorButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
- anchors.topMargin: Style.marginXS * scaling
- anchors.leftMargin: Style.marginM * scaling
- anchors.rightMargin: Style.marginM * scaling
- height: Style.barHeight * scaling
+ anchors.topMargin: Style.marginXS
+ anchors.leftMargin: Style.marginM
+ anchors.rightMargin: Style.marginM
+ height: Style.barHeight
visible: MediaService.getAvailablePlayers().length > 1
- radius: Style.radiusM * scaling
+ radius: Style.radiusM
color: Color.transparent
property var currentPlayer: MediaService.getAvailablePlayers()[MediaService.selectedPlayerIndex]
RowLayout {
anchors.fill: parent
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
NIcon {
icon: "caret-down"
- pointSize: Style.fontSizeXXL * scaling
+ pointSize: Style.fontSizeXXL
color: Color.mOnSurfaceVariant
}
NText {
text: playerSelectorButton.currentPlayer ? playerSelectorButton.currentPlayer.identity : ""
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
}
@@ -170,7 +170,7 @@ NBox {
NContextMenu {
id: playerContextMenu
parent: root
- width: 200 * scaling
+ width: 200
onTriggered: function (action) {
var index = parseInt(action)
@@ -184,14 +184,14 @@ NBox {
ColumnLayout {
anchors.fill: parent
- anchors.margins: Style.marginM * scaling
+ anchors.margins: Style.marginM
// No media player detected
ColumnLayout {
id: fallback
visible: !main.visible
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
Item {
Layout.fillWidth: true
@@ -204,12 +204,12 @@ NBox {
ColumnLayout {
anchors.centerIn: parent
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Item {
Layout.alignment: Qt.AlignHCenter
- Layout.preferredWidth: Style.fontSizeXXXL * 4 * scaling
- Layout.preferredHeight: Style.fontSizeXXXL * 4 * scaling
+ Layout.preferredWidth: Style.fontSizeXXXL * 4
+ Layout.preferredHeight: Style.fontSizeXXXL * 4
// Pulsating audio circles (background)
Repeater {
@@ -258,7 +258,7 @@ NBox {
NIcon {
anchors.centerIn: parent
icon: "disc"
- pointSize: Style.fontSizeXXXL * 3 * scaling
+ pointSize: Style.fontSizeXXXL * 3
color: Color.mOnSurfaceVariant
RotationAnimator on rotation {
@@ -274,7 +274,7 @@ NBox {
// Descriptive text
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
- spacing: Style.marginXS * scaling
+ spacing: Style.marginXS
}
}
}
@@ -290,23 +290,23 @@ NBox {
id: main
visible: MediaService.currentPlayer && MediaService.canPlay
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
// Spacer to push content down
Item {
- Layout.preferredHeight: Style.marginM * scaling
+ Layout.preferredHeight: Style.marginM
}
// Metadata at the bottom left
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
- spacing: Style.marginXS * scaling
+ spacing: Style.marginXS
NText {
visible: MediaService.trackTitle !== ""
text: MediaService.trackTitle
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
elide: Text.ElideRight
wrapMode: Text.Wrap
@@ -318,7 +318,7 @@ NBox {
visible: MediaService.trackArtist !== ""
text: MediaService.trackArtist
color: Color.mPrimary
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
elide: Text.ElideRight
Layout.fillWidth: true
}
@@ -327,7 +327,7 @@ NBox {
visible: MediaService.trackAlbum !== ""
text: MediaService.trackAlbum
color: Color.mOnSurfaceVariant
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
elide: Text.ElideRight
Layout.fillWidth: true
}
@@ -338,7 +338,7 @@ NBox {
id: progressWrapper
visible: (MediaService.currentPlayer && MediaService.trackLength > 0)
Layout.fillWidth: true
- height: Style.baseWidgetSize * 0.5 * scaling
+ height: Style.baseWidgetSize * 0.5
property real localSeekRatio: -1
property real lastSentSeekRatio: -1
@@ -376,7 +376,7 @@ NBox {
stepSize: 0
snapAlways: false
enabled: MediaService.trackLength > 0 && MediaService.canSeek
- heightRatio: 0.65
+ heightRatio: 0.6
onMoved: {
progressWrapper.localSeekRatio = value
@@ -408,12 +408,12 @@ NBox {
// Spacer to push media controls down
Item {
- Layout.preferredHeight: Style.marginL * scaling
+ Layout.preferredHeight: Style.marginL
}
// Media controls
RowLayout {
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
diff --git a/Modules/ControlCenter/Cards/ProfileCard.qml b/Modules/ControlCenter/Cards/ProfileCard.qml
new file mode 100644
index 00000000..7c891182
--- /dev/null
+++ b/Modules/ControlCenter/Cards/ProfileCard.qml
@@ -0,0 +1,113 @@
+import QtQuick
+import QtQuick.Effects
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Io
+import Quickshell.Widgets
+import qs.Modules.Settings
+import qs.Modules.ControlCenter
+import qs.Commons
+import qs.Services
+import qs.Widgets
+
+// Header card with avatar, user and quick actions
+NBox {
+ id: root
+
+ property string uptimeText: "--"
+
+ RowLayout {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.margins: Style.marginM
+ spacing: Style.marginM
+
+ NImageCircled {
+ Layout.preferredWidth: Math.round(Style.baseWidgetSize * 1.25 * Style.uiScaleRatio)
+ Layout.preferredHeight: Math.round(Style.baseWidgetSize * 1.25 * Style.uiScaleRatio)
+ imagePath: Settings.preprocessPath(Settings.data.general.avatarImage)
+ fallbackIcon: "person"
+ borderColor: Color.mPrimary
+ borderWidth: Math.max(1, Style.borderM)
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: Style.marginXXS
+ NText {
+ text: Quickshell.env("USER") || "user"
+ font.weight: Style.fontWeightBold
+ font.capitalization: Font.Capitalize
+ }
+ NText {
+ text: I18n.tr("system.uptime", {
+ "uptime": uptimeText
+ })
+ pointSize: Style.fontSizeS
+ color: Color.mOnSurfaceVariant
+ }
+ }
+
+ RowLayout {
+ spacing: Style.marginS
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ Item {
+ Layout.fillWidth: true
+ }
+ NIconButton {
+ icon: "settings"
+ tooltipText: I18n.tr("tooltips.open-settings")
+ onClicked: {
+ settingsPanel.requestedTab = SettingsPanel.Tab.General
+ settingsPanel.open()
+ }
+ }
+
+ NIconButton {
+ icon: "power"
+ tooltipText: I18n.tr("tooltips.session-menu")
+ onClicked: {
+ sessionMenuPanel.open()
+ controlCenterPanel.close()
+ }
+ }
+
+ NIconButton {
+ icon: "close"
+ tooltipText: I18n.tr("tooltips.close")
+ onClicked: {
+ controlCenterPanel.close()
+ }
+ }
+ }
+ }
+
+ // ----------------------------------
+ // Uptime
+ Timer {
+ interval: 60000
+ repeat: true
+ running: true
+ onTriggered: uptimeProcess.running = true
+ }
+
+ Process {
+ id: uptimeProcess
+ command: ["cat", "/proc/uptime"]
+ running: true
+
+ stdout: StdioCollector {
+ onStreamFinished: {
+ var uptimeSeconds = parseFloat(this.text.trim().split(' ')[0])
+ uptimeText = Time.formatVagueHumanReadableDuration(uptimeSeconds)
+ uptimeProcess.running = false
+ }
+ }
+ }
+
+ function updateSystemInfo() {
+ uptimeProcess.running = true
+ }
+}
diff --git a/Modules/ControlCenter/Cards/ShortcutsCard.qml b/Modules/ControlCenter/Cards/ShortcutsCard.qml
new file mode 100644
index 00000000..2100a76b
--- /dev/null
+++ b/Modules/ControlCenter/Cards/ShortcutsCard.qml
@@ -0,0 +1,84 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import qs.Commons
+import qs.Services
+import qs.Widgets
+import qs.Modules.ControlCenter
+import qs.Modules.ControlCenter.Cards
+
+RowLayout {
+ Layout.fillWidth: true
+ spacing: Style.marginL
+
+ NBox {
+ Layout.fillWidth: true
+ Layout.preferredHeight: root.shortcutsHeight
+
+ RowLayout {
+ id: leftContent
+ anchors.fill: parent
+ spacing: Style.marginS
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ Repeater {
+ model: Settings.data.controlCenter.shortcuts.left
+ delegate: ControlCenterWidgetLoader {
+ Layout.fillWidth: false
+ widgetId: (modelData.id !== undefined ? modelData.id : "")
+ widgetProps: {
+ "screen": root.modelData || null,
+ "widgetId": modelData.id,
+ "section": "quickSettings",
+ "sectionWidgetIndex": index,
+ "sectionWidgetsCount": Settings.data.controlCenter.shortcuts.left.length
+ }
+ Layout.alignment: Qt.AlignVCenter
+ }
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+ }
+ }
+
+ NBox {
+ Layout.fillWidth: true
+ Layout.preferredHeight: root.shortcutsHeight
+
+ RowLayout {
+ id: rightContent
+ anchors.fill: parent
+ spacing: Style.marginS
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ Repeater {
+ model: Settings.data.controlCenter.shortcuts.right
+ delegate: ControlCenterWidgetLoader {
+ Layout.fillWidth: false
+ widgetId: (modelData.id !== undefined ? modelData.id : "")
+ widgetProps: {
+ "screen": root.modelData || null,
+ "widgetId": modelData.id,
+ "section": "quickSettings",
+ "sectionWidgetIndex": index,
+ "sectionWidgetsCount": Settings.data.controlCenter.shortcuts.right.length
+ }
+ Layout.alignment: Qt.AlignVCenter
+ }
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+ }
+ }
+}
diff --git a/Modules/ControlCenter/Cards/SystemMonitorCard.qml b/Modules/ControlCenter/Cards/SystemMonitorCard.qml
index 351d4c68..3e23aa89 100644
--- a/Modules/ControlCenter/Cards/SystemMonitorCard.qml
+++ b/Modules/ControlCenter/Cards/SystemMonitorCard.qml
@@ -9,47 +9,50 @@ import qs.Widgets
NBox {
id: root
- GridLayout {
+ Item {
id: content
anchors.fill: parent
- anchors.margins: Style.marginXS * scaling
- columns: 2
- rows: 2
- columnSpacing: Style.marginS * scaling
- rowSpacing: Style.marginS * scaling
+ anchors.margins: Style.marginS
- NCircleStat {
- value: SystemStatService.cpuUsage
- icon: "cpu-usage"
- flat: true
- contentScale: 0.8
- Layout.fillWidth: true
- Layout.fillHeight: true
- }
- NCircleStat {
- value: SystemStatService.cpuTemp
- suffix: "°C"
- icon: "cpu-temperature"
- flat: true
- contentScale: 0.8
- Layout.fillWidth: true
- Layout.fillHeight: true
- }
- NCircleStat {
- value: SystemStatService.memPercent
- icon: "memory"
- flat: true
- contentScale: 0.8
- Layout.fillWidth: true
- Layout.fillHeight: true
- }
- NCircleStat {
- value: SystemStatService.diskPercent
- icon: "storage"
- flat: true
- contentScale: 0.8
- Layout.fillWidth: true
- Layout.fillHeight: true
+ property int widgetHeight: Math.round(65 * Style.uiScaleRatio)
+
+ ColumnLayout {
+ anchors.centerIn: parent
+ spacing: 0
+
+ NCircleStat {
+ value: SystemStatService.cpuUsage
+ icon: "cpu-usage"
+ flat: true
+ contentScale: 0.8
+ height: content.widgetHeight
+ Layout.alignment: Qt.AlignHCenter
+ }
+ NCircleStat {
+ value: SystemStatService.cpuTemp
+ suffix: "°C"
+ icon: "cpu-temperature"
+ flat: true
+ contentScale: 0.8
+ height: content.widgetHeight
+ Layout.alignment: Qt.AlignHCenter
+ }
+ NCircleStat {
+ value: SystemStatService.memPercent
+ icon: "memory"
+ flat: true
+ contentScale: 0.8
+ height: content.widgetHeight
+ Layout.alignment: Qt.AlignHCenter
+ }
+ NCircleStat {
+ value: SystemStatService.diskPercent
+ icon: "storage"
+ flat: true
+ contentScale: 0.8
+ height: content.widgetHeight
+ Layout.alignment: Qt.AlignHCenter
+ }
}
}
}
diff --git a/Modules/ControlCenter/Cards/TopCard.qml b/Modules/ControlCenter/Cards/TopCard.qml
deleted file mode 100644
index d68d84be..00000000
--- a/Modules/ControlCenter/Cards/TopCard.qml
+++ /dev/null
@@ -1,330 +0,0 @@
-import QtQuick
-import QtQuick.Effects
-import QtQuick.Layouts
-import Quickshell
-import Quickshell.Io
-import Quickshell.Widgets
-import Quickshell.Services.UPower
-import qs.Modules.Settings
-import qs.Modules.ControlCenter
-import qs.Commons
-import qs.Services
-import qs.Widgets
-
-// Header card with avatar, user and quick actions
-NBox {
- id: root
-
- property string uptimeText: "--"
- property real spacing: Style.marginS * scaling
- readonly property bool hasPP: PowerProfileService.available
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: Style.marginM * scaling
-
- // Profile, Uptime, Settings, SessionMenu, Close
- RowLayout {
- id: content
-
- spacing: root.spacing
- Layout.alignment: Qt.AlignVCenter
-
- NImageCircled {
- width: Style.baseWidgetSize * 1.25 * scaling
- height: Style.baseWidgetSize * 1.25 * scaling
- imagePath: Settings.data.general.avatarImage
- fallbackIcon: "person"
- borderColor: Color.mPrimary
- borderWidth: Math.max(1, Style.borderM * scaling)
- Layout.alignment: Qt.AlignVCenter
- Layout.topMargin: Style.marginXS * scaling
- }
-
- ColumnLayout {
- Layout.fillWidth: true
- spacing: Style.marginXXS * scaling
- NText {
- text: Quickshell.env("USER") || "user"
- font.weight: Style.fontWeightBold
- font.capitalization: Font.Capitalize
- }
- NText {
- text: I18n.tr("system.uptime", {
- "uptime": uptimeText
- })
- pointSize: Style.fontSizeS * scaling
- color: Color.mOnSurfaceVariant
- }
- }
-
- Item {
- Layout.fillWidth: true
- }
-
- RowLayout {
- spacing: root.spacing
- Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
-
- NIconButton {
- baseSize: Style.baseWidgetSize * 0.9
- icon: "settings"
- tooltipText: I18n.tr("tooltips.open-settings")
- onClicked: {
- settingsPanel.requestedTab = SettingsPanel.Tab.General
- settingsPanel.open()
- }
- }
-
- NIconButton {
- baseSize: Style.baseWidgetSize * 0.9
- icon: "power"
- tooltipText: I18n.tr("tooltips.session-menu")
- onClicked: {
- sessionMenuPanel.open()
- controlCenterPanel.close()
- }
- }
-
- NIconButton {
- baseSize: Style.baseWidgetSize * 0.9
- icon: "close"
- tooltipText: I18n.tr("tooltips.close")
- onClicked: {
- controlCenterPanel.close()
- }
- }
- }
- }
-
- RowLayout {
- id: utilitiesRow
- Layout.alignment: Qt.AlignVCenter
- Layout.topMargin: Style.marginM * scaling
- Layout.bottomMargin: Style.marginM * scaling
- Layout.fillWidth: true
-
- // Left group - Media & Display
- Rectangle {
- color: Color.mSurface
- radius: Style.radiusM * scaling
- Layout.preferredHeight: Style.baseWidgetSize * 1.2 * scaling
- Layout.preferredWidth: childrenRect.width + (Style.marginS * scaling * 2)
-
- RowLayout {
- anchors.centerIn: parent
- spacing: Style.marginM * scaling
-
- // Screen Recorder
- NIconButton {
- baseSize: Style.baseWidgetSize * 0.9
- icon: "camera-video"
- visible: ProgramCheckerService.gpuScreenRecorderAvailable
- tooltipText: ScreenRecorderService.isRecording ? I18n.tr("tooltips.stop-screen-recording") : I18n.tr("tooltips.start-screen-recording")
- colorBg: ScreenRecorderService.isRecording ? Color.mPrimary : Color.mSurfaceVariant
- colorFg: ScreenRecorderService.isRecording ? Color.mOnPrimary : Color.mPrimary
- onClicked: {
- ScreenRecorderService.toggleRecording()
- if (!ScreenRecorderService.isRecording) {
- var panel = PanelService.getPanel("controlCenterPanel")
- panel?.close()
- }
- }
- }
-
- // Wallpaper
- NIconButton {
- baseSize: Style.baseWidgetSize * 0.9
- visible: Settings.data.wallpaper.enabled
- icon: "wallpaper-selector"
- tooltipText: I18n.tr("tooltips.wallpaper-selector")
- onClicked: PanelService.getPanel("wallpaperPanel")?.toggle(this)
- onRightClicked: WallpaperService.setRandomWallpaper()
- }
-
- // Night Light
- NIconButton {
- baseSize: Style.baseWidgetSize * 0.9
- visible: ProgramCheckerService.wlsunsetAvailable
- colorBg: Settings.data.nightLight.forced ? Color.mPrimary : Color.transparent
- colorFg: Settings.data.nightLight.forced ? Color.mOnPrimary : Color.mPrimary
- icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off"
- tooltipText: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? I18n.tr("tooltips.night-light-forced") : I18n.tr("tooltips.night-light-enabled")) : I18n.tr("tooltips.night-light-disabled")
- onClicked: {
- if (!Settings.data.nightLight.enabled) {
- Settings.data.nightLight.enabled = true
- Settings.data.nightLight.forced = false
- } else if (Settings.data.nightLight.enabled && !Settings.data.nightLight.forced) {
- Settings.data.nightLight.forced = true
- } else {
- Settings.data.nightLight.enabled = false
- Settings.data.nightLight.forced = false
- }
- }
-
- onRightClicked: {
- var settingsPanel = PanelService.getPanel("settingsPanel")
- settingsPanel.requestedTab = SettingsPanel.Tab.Display
- settingsPanel.open()
- }
- }
- }
- }
-
- // Spacer
- Item {
- Layout.fillWidth: true
- }
-
- // Center group - Network & Caffeine
- Rectangle {
- color: Color.mSurface
- radius: Style.radiusM * scaling
- Layout.preferredHeight: Style.baseWidgetSize * 1.2 * scaling
- Layout.preferredWidth: childrenRect.width + (Style.marginS * scaling * 2)
-
- RowLayout {
- anchors.centerIn: parent
- spacing: Style.marginM * scaling
-
- // Wifi
- NIconButton {
- id: wifiButton
- baseSize: Style.baseWidgetSize * 0.9
- tooltipText: I18n.tr("tooltips.manage-wifi")
- icon: {
- try {
- if (NetworkService.ethernetConnected) {
- return "ethernet"
- }
- let connected = false
- let signalStrength = 0
- for (const net in NetworkService.networks) {
- if (NetworkService.networks[net].connected) {
- connected = true
- signalStrength = NetworkService.networks[net].signal
- break
- }
- }
- return connected ? NetworkService.signalIcon(signalStrength) : "wifi-off"
- } catch (error) {
- Logger.error("Wi-Fi", "Error getting icon:", error)
- return "signal_wifi_bad"
- }
- }
- onClicked: PanelService.getPanel("wifiPanel")?.toggle(this)
- onRightClicked: PanelService.getPanel("wifiPanel")?.toggle(this)
- }
-
- // Bluetooth
- NIconButton {
- baseSize: Style.baseWidgetSize * 0.9
- tooltipText: I18n.tr("tooltips.bluetooth-devices")
- icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off"
- onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this)
- onRightClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this)
- }
-
- // Caffeine (Keep Awake)
- NIconButton {
- baseSize: Style.baseWidgetSize * 0.9
- icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off"
- tooltipText: IdleInhibitorService.isInhibited ? I18n.tr("tooltips.disable-keep-awake") : I18n.tr("tooltips.enable-keep-awake")
- colorBg: IdleInhibitorService.isInhibited ? Color.mPrimary : Color.mSurfaceVariant
- colorFg: IdleInhibitorService.isInhibited ? Color.mOnPrimary : Color.mPrimary
- onClicked: {
- IdleInhibitorService.manualToggle()
- }
- }
- }
- }
-
- // Spacer
- Item {
- Layout.fillWidth: true
- }
-
- // Right group - Power Profiles
- Rectangle {
- color: Color.mSurface
- radius: Style.radiusM * scaling
- Layout.preferredHeight: Style.baseWidgetSize * 1.2 * scaling
- Layout.preferredWidth: childrenRect.width + (Style.marginS * scaling * 2)
-
- RowLayout {
- anchors.centerIn: parent
- spacing: Style.marginM * scaling
-
- // Performance
- NIconButton {
- baseSize: Style.baseWidgetSize * 0.9
- icon: PowerProfileService.getIcon(PowerProfile.Performance)
- tooltipText: I18n.tr("tooltips.set-power-profile", {
- "profile": PowerProfileService.getName(PowerProfile.Performance)
- })
- enabled: hasPP
- opacity: enabled ? Style.opacityFull : Style.opacityMedium
- colorBg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mPrimary : Color.mSurfaceVariant
- colorFg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mOnPrimary : Color.mPrimary
- onClicked: PowerProfileService.setProfile(PowerProfile.Performance)
- }
-
- // Balanced
- NIconButton {
- baseSize: Style.baseWidgetSize * 0.9
- icon: PowerProfileService.getIcon(PowerProfile.Balanced)
- tooltipText: I18n.tr("tooltips.set-power-profile", {
- "profile": PowerProfileService.getName(PowerProfile.Balanced)
- })
- enabled: hasPP
- opacity: enabled ? Style.opacityFull : Style.opacityMedium
- colorBg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mPrimary : Color.mSurfaceVariant
- colorFg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnPrimary : Color.mPrimary
- onClicked: PowerProfileService.setProfile(PowerProfile.Balanced)
- }
-
- // Eco
- NIconButton {
- baseSize: Style.baseWidgetSize * 0.9
- icon: PowerProfileService.getIcon(PowerProfile.PowerSaver)
- tooltipText: I18n.tr("tooltips.set-power-profile", {
- "profile": PowerProfileService.getName(PowerProfile.PowerSaver)
- })
- enabled: hasPP
- opacity: enabled ? Style.opacityFull : Style.opacityMedium
- colorBg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mPrimary : Color.mSurfaceVariant
- colorFg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mOnPrimary : Color.mPrimary
- onClicked: PowerProfileService.setProfile(PowerProfile.PowerSaver)
- }
- }
- }
- }
- }
-
- // ----------------------------------
- // Uptime
- Timer {
- interval: 60000
- repeat: true
- running: true
- onTriggered: uptimeProcess.running = true
- }
-
- Process {
- id: uptimeProcess
- command: ["cat", "/proc/uptime"]
- running: true
-
- stdout: StdioCollector {
- onStreamFinished: {
- var uptimeSeconds = parseFloat(this.text.trim().split(' ')[0])
- uptimeText = Time.formatVagueHumanReadableDuration(uptimeSeconds)
- uptimeProcess.running = false
- }
- }
- }
-
- function updateSystemInfo() {
- uptimeProcess.running = true
- }
-}
diff --git a/Modules/ControlCenter/Cards/WeatherCard.qml b/Modules/ControlCenter/Cards/WeatherCard.qml
index d9e8510a..e6b366ff 100644
--- a/Modules/ControlCenter/Cards/WeatherCard.qml
+++ b/Modules/ControlCenter/Cards/WeatherCard.qml
@@ -16,28 +16,28 @@ NBox {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
- anchors.margins: Style.marginM * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginM
+ spacing: Style.marginM
clip: true
RowLayout {
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
NIcon {
Layout.alignment: Qt.AlignVCenter
icon: weatherReady ? LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode) : ""
- pointSize: Style.fontSizeXXXL * 1.75 * scaling
+ pointSize: Style.fontSizeXXXL * 1.75
color: Color.mPrimary
}
ColumnLayout {
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
NText {
text: {
// Ensure the name is not too long if one had to specify the country
const chunks = Settings.data.location.name.split(",")
return chunks[0]
}
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
}
@@ -57,13 +57,13 @@ NBox {
temp = Math.round(temp)
return `${temp}°${suffix}`
}
- pointSize: Style.fontSizeXL * scaling
+ pointSize: Style.fontSizeXL
font.weight: Style.fontWeightBold
}
NText {
text: weatherReady ? `(${LocationService.data.weather.timezone_abbreviation})` : ""
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
visible: LocationService.data.weather
}
@@ -80,12 +80,12 @@ NBox {
visible: weatherReady
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Repeater {
model: weatherReady ? LocationService.data.weather.daily.time : []
delegate: ColumnLayout {
Layout.alignment: Qt.AlignHCenter
- spacing: Style.marginS * scaling
+ spacing: Style.marginXS
NText {
text: {
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"))
@@ -97,7 +97,7 @@ NBox {
NIcon {
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index])
- pointSize: Style.fontSizeXXL * 1.6 * scaling
+ pointSize: Style.fontSizeXXL * 1.6
color: Color.mPrimary
}
NText {
@@ -113,7 +113,7 @@ NBox {
min = Math.round(min)
return `${max}°/${min}°`
}
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
}
}
diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml
index c2290cc0..599dddb6 100644
--- a/Modules/ControlCenter/ControlCenterPanel.qml
+++ b/Modules/ControlCenter/ControlCenterPanel.qml
@@ -10,9 +10,39 @@ import qs.Widgets
NPanel {
id: root
- preferredWidth: 480
- preferredHeight: 580
panelKeyboardFocus: true
+ preferredWidth: Math.round(460 * Style.uiScaleRatio)
+ preferredHeight: {
+ var height = 0
+ var count = 0
+ for (var i = 0; i < Settings.data.controlCenter.cards.length; i++) {
+ const card = Settings.data.controlCenter.cards[i]
+ if (!card.enabled) {
+ continue
+ }
+ count++
+ switch (card.id) {
+ case "profile-card":
+ height += profileHeight
+ break
+ case "shortcuts-card":
+ height += shortcutsHeight
+ break
+ case "audio-card":
+ height += audioHeight
+ break
+ case "weather-card":
+ height += weatherHeight
+ break
+ case "media-sysmon-card":
+ height += mediaSysMonHeight
+ break
+ default:
+ break
+ }
+ }
+ return height + (count + 1) * Style.marginL
+ }
// Positioning
readonly property string controlCenterPosition: Settings.data.controlCenter.position
@@ -23,46 +53,97 @@ NPanel {
panelAnchorBottom: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.startsWith("bottom_")
panelAnchorTop: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.startsWith("top_")
+ readonly property int profileHeight: Math.round(64 * Style.uiScaleRatio)
+ readonly property int shortcutsHeight: Math.round(52 * Style.uiScaleRatio)
+ readonly property int audioHeight: Math.round(60 * Style.uiScaleRatio)
+ readonly property int weatherHeight: Math.round(190 * Style.uiScaleRatio)
+ readonly property int mediaSysMonHeight: Math.round(260 * Style.uiScaleRatio)
+
panelContent: Item {
id: content
- property real cardSpacing: Style.marginL * scaling
-
- // Layout content
ColumnLayout {
id: layout
- anchors.fill: parent
- anchors.margins: content.cardSpacing
- spacing: content.cardSpacing
+ x: Style.marginL
+ y: Style.marginL
+ width: parent.width - (Style.marginL * 2)
+ spacing: Style.marginL
- // Top Card: profile + utilities
- TopCard {
- Layout.fillWidth: true
- Layout.preferredHeight: Math.max(124 * scaling)
+ Repeater {
+ model: Settings.data.controlCenter.cards
+ Loader {
+ active: modelData.enabled
+ visible: active
+ Layout.fillWidth: true
+ Layout.preferredHeight: {
+ switch (modelData.id) {
+ case "profile-card":
+ return profileHeight
+ case "shortcuts-card":
+ return shortcutsHeight
+ case "audio-card":
+ return audioHeight
+ case "weather-card":
+ return weatherHeight
+ case "media-sysmon-card":
+ return mediaSysMonHeight
+ default:
+ return 0
+ }
+ }
+ sourceComponent: {
+ switch (modelData.id) {
+ case "profile-card":
+ return profileCard
+ case "shortcuts-card":
+ return shortcutsCard
+ case "audio-card":
+ return audioCard
+ case "weather-card":
+ return weatherCard
+ case "media-sysmon-card":
+ return mediaSysMonCard
+ }
+ }
+ }
}
+ }
- // Weather
- WeatherCard {
- Layout.fillWidth: true
- Layout.preferredHeight: Math.max(196 * scaling)
- }
+ Component {
+ id: profileCard
+ ProfileCard {}
+ }
- // Media + stats column
+ Component {
+ id: shortcutsCard
+ ShortcutsCard {}
+ }
+
+ Component {
+ id: audioCard
+ AudioCard {}
+ }
+
+ Component {
+ id: weatherCard
+ WeatherCard {}
+ }
+
+ Component {
+ id: mediaSysMonCard
RowLayout {
- Layout.fillWidth: true
- Layout.preferredHeight: Math.max(196 * scaling)
- spacing: content.cardSpacing
+ spacing: Style.marginL
// Media card
MediaCard {
- Layout.preferredWidth: Math.max(270 * scaling)
+ Layout.fillWidth: true
Layout.fillHeight: true
}
// System monitors combined in one card
SystemMonitorCard {
- Layout.preferredWidth: Math.max(160 * scaling)
- Layout.preferredHeight: Math.max(196 * scaling)
+ Layout.preferredWidth: Math.round(Style.baseWidgetSize * 2.625)
+ Layout.fillHeight: true
}
}
}
diff --git a/Modules/ControlCenter/ControlCenterWidgetLoader.qml b/Modules/ControlCenter/ControlCenterWidgetLoader.qml
new file mode 100644
index 00000000..5dbe1338
--- /dev/null
+++ b/Modules/ControlCenter/ControlCenterWidgetLoader.qml
@@ -0,0 +1,64 @@
+import QtQuick
+import Quickshell
+import qs.Services
+import qs.Commons
+
+Item {
+ id: root
+
+ property string widgetId: ""
+ property var widgetProps: ({})
+ property string screenName: widgetProps && widgetProps.screen ? widgetProps.screen.name : ""
+ property string section: widgetProps && widgetProps.section || ""
+ property int sectionIndex: widgetProps && widgetProps.sectionWidgetIndex || 0
+
+ // Don't reserve space unless the loaded widget is really visible
+ implicitWidth: getImplicitSize(loader.item, "implicitWidth")
+ implicitHeight: getImplicitSize(loader.item, "implicitHeight")
+
+ function getImplicitSize(item, prop) {
+ return (item && item.visible) ? item[prop] : 0
+ }
+
+ Loader {
+ id: loader
+ anchors.fill: parent
+ active: widgetId !== ""
+ asynchronous: false
+ sourceComponent: {
+ if (!active) {
+ return null
+ }
+ return ControlCenterWidgetRegistry.getWidget(widgetId)
+ }
+
+ onLoaded: {
+ if (item && widgetProps) {
+ // Apply properties to loaded widget
+ for (var prop in widgetProps) {
+ if (item.hasOwnProperty(prop)) {
+ item[prop] = widgetProps[prop]
+ }
+ }
+ }
+
+ if (item.hasOwnProperty("onLoaded")) {
+ item.onLoaded()
+ }
+
+ //Logger.log("ControlCenterWidgetLoader", "Loaded", widgetId, "on screen", item.screen.name)
+ }
+
+ Component.onDestruction: {
+ // Explicitly clear references
+ widgetProps = null
+ }
+ }
+
+ // Error handling
+ onWidgetIdChanged: {
+ if (widgetId && !ControlCenterWidgetRegistry.hasWidget(widgetId)) {
+ Logger.warn("ControlCenterWidgetLoader", "Widget not found in registry:", widgetId)
+ }
+ }
+}
diff --git a/Modules/ControlCenter/Widgets/Bluetooth.qml b/Modules/ControlCenter/Widgets/Bluetooth.qml
new file mode 100644
index 00000000..e4549a31
--- /dev/null
+++ b/Modules/ControlCenter/Widgets/Bluetooth.qml
@@ -0,0 +1,13 @@
+import QtQuick.Layouts
+import Quickshell
+import qs.Commons
+import qs.Services
+import qs.Widgets
+
+NIconButtonHot {
+ property ShellScreen screen
+
+ icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off"
+ tooltipText: I18n.tr("quickSettings.bluetooth.tooltip.action")
+ onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this)
+}
diff --git a/Modules/ControlCenter/Widgets/KeepAwake.qml b/Modules/ControlCenter/Widgets/KeepAwake.qml
new file mode 100644
index 00000000..96d60733
--- /dev/null
+++ b/Modules/ControlCenter/Widgets/KeepAwake.qml
@@ -0,0 +1,14 @@
+import QtQuick.Layouts
+import Quickshell
+import qs.Commons
+import qs.Services
+import qs.Widgets
+
+NIconButtonHot {
+ property ShellScreen screen
+
+ icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off"
+ hot: IdleInhibitorService.isInhibited
+ tooltipText: I18n.tr("quickSettings.keepAwake.tooltip.action")
+ onClicked: IdleInhibitorService.manualToggle()
+}
diff --git a/Modules/ControlCenter/Widgets/NightLight.qml b/Modules/ControlCenter/Widgets/NightLight.qml
new file mode 100644
index 00000000..eb338882
--- /dev/null
+++ b/Modules/ControlCenter/Widgets/NightLight.qml
@@ -0,0 +1,32 @@
+import QtQuick.Layouts
+import Quickshell
+import qs.Commons
+import qs.Services
+import qs.Widgets
+
+NIconButtonHot {
+ property ShellScreen screen
+
+ enabled: ProgramCheckerService.wlsunsetAvailable
+ icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off"
+ hot: !Settings.data.nightLight.enabled || Settings.data.nightLight.forced
+ tooltipText: I18n.tr("quickSettings.nightLight.tooltip.action")
+
+ onClicked: {
+ if (!Settings.data.nightLight.enabled) {
+ Settings.data.nightLight.enabled = true
+ Settings.data.nightLight.forced = false
+ } else if (Settings.data.nightLight.enabled && !Settings.data.nightLight.forced) {
+ Settings.data.nightLight.forced = true
+ } else {
+ Settings.data.nightLight.enabled = false
+ Settings.data.nightLight.forced = false
+ }
+ }
+
+ onRightClicked: {
+ var settingsPanel = PanelService.getPanel("settingsPanel")
+ settingsPanel.requestedTab = SettingsPanel.Tab.Display
+ settingsPanel.open()
+ }
+}
diff --git a/Modules/ControlCenter/Widgets/Notifications.qml b/Modules/ControlCenter/Widgets/Notifications.qml
new file mode 100644
index 00000000..ae291517
--- /dev/null
+++ b/Modules/ControlCenter/Widgets/Notifications.qml
@@ -0,0 +1,15 @@
+import QtQuick.Layouts
+import Quickshell
+import qs.Commons
+import qs.Services
+import qs.Widgets
+
+NIconButtonHot {
+ property ShellScreen screen
+
+ icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell"
+ hot: Settings.data.notifications.doNotDisturb
+ tooltipText: I18n.tr("quickSettings.notifications.tooltip.action")
+ onClicked: PanelService.getPanel("notificationHistoryPanel")?.toggle(this)
+ onRightClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
+}
diff --git a/Modules/ControlCenter/Widgets/PowerProfile.qml b/Modules/ControlCenter/Widgets/PowerProfile.qml
new file mode 100644
index 00000000..ca5f497f
--- /dev/null
+++ b/Modules/ControlCenter/Widgets/PowerProfile.qml
@@ -0,0 +1,21 @@
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Services.UPower
+import qs.Commons
+import qs.Services
+import qs.Widgets
+
+// Performance
+NIconButtonHot {
+ property ShellScreen screen
+
+ readonly property bool hasPP: PowerProfileService.available
+
+ enabled: hasPP
+ icon: PowerProfileService.getIcon()
+ hot: !PowerProfileService.isDefault()
+ tooltipText: I18n.tr("quickSettings.powerProfile.tooltip.action")
+ onClicked: {
+ PowerProfileService.cycleProfile()
+ }
+}
diff --git a/Modules/ControlCenter/Widgets/ScreenRecorder.qml b/Modules/ControlCenter/Widgets/ScreenRecorder.qml
new file mode 100644
index 00000000..7a58c8f3
--- /dev/null
+++ b/Modules/ControlCenter/Widgets/ScreenRecorder.qml
@@ -0,0 +1,21 @@
+import QtQuick.Layouts
+import Quickshell
+import qs.Commons
+import qs.Services
+import qs.Widgets
+
+NIconButtonHot {
+ property ShellScreen screen
+
+ enabled: ProgramCheckerService.gpuScreenRecorderAvailable
+ icon: "camera-video"
+ hot: ScreenRecorderService.isRecording
+ tooltipText: I18n.tr("quickSettings.screenRecorder.tooltip.action")
+ onClicked: {
+ ScreenRecorderService.toggleRecording()
+ if (!ScreenRecorderService.isRecording) {
+ var panel = PanelService.getPanel("controlCenterPanel")
+ panel?.close()
+ }
+ }
+}
diff --git a/Modules/ControlCenter/Widgets/WallpaperSelector.qml b/Modules/ControlCenter/Widgets/WallpaperSelector.qml
new file mode 100644
index 00000000..ff513bc1
--- /dev/null
+++ b/Modules/ControlCenter/Widgets/WallpaperSelector.qml
@@ -0,0 +1,15 @@
+import QtQuick.Layouts
+import Quickshell
+import qs.Commons
+import qs.Services
+import qs.Widgets
+
+NIconButtonHot {
+ property ShellScreen screen
+
+ enabled: Settings.data.wallpaper.enabled
+ icon: "wallpaper-selector"
+ tooltipText: I18n.tr("quickSettings.wallpaperSelector.tooltip.action")
+ onClicked: PanelService.getPanel("wallpaperPanel")?.toggle(this)
+ onRightClicked: WallpaperService.setRandomWallpaper()
+}
diff --git a/Modules/ControlCenter/Widgets/WiFi.qml b/Modules/ControlCenter/Widgets/WiFi.qml
new file mode 100644
index 00000000..faaedc73
--- /dev/null
+++ b/Modules/ControlCenter/Widgets/WiFi.qml
@@ -0,0 +1,33 @@
+import QtQuick.Layouts
+import Quickshell
+import qs.Commons
+import qs.Services
+import qs.Widgets
+
+NIconButtonHot {
+ property ShellScreen screen
+
+ icon: {
+ try {
+ if (NetworkService.ethernetConnected) {
+ return "ethernet"
+ }
+ let connected = false
+ let signalStrength = 0
+ for (const net in NetworkService.networks) {
+ if (NetworkService.networks[net].connected) {
+ connected = true
+ signalStrength = NetworkService.networks[net].signal
+ break
+ }
+ }
+ return connected ? NetworkService.signalIcon(signalStrength) : "wifi-off"
+ } catch (error) {
+ Logger.error("Wi-Fi", "Error getting icon:", error)
+ return "signal_wifi_bad"
+ }
+ }
+
+ tooltipText: I18n.tr("quickSettings.wifi.tooltip.action")
+ onClicked: PanelService.getPanel("wifiPanel")?.toggle(this)
+}
diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml
index d99fdd5e..56a932e5 100644
--- a/Modules/Dock/Dock.qml
+++ b/Modules/Dock/Dock.qml
@@ -16,7 +16,7 @@ Variants {
id: root
required property ShellScreen modelData
- property real scaling: ScalingService.getScreenScale(modelData)
+
property bool barIsReady: modelData ? BarService.isBarReady(modelData.name) : false
Connections {
@@ -28,15 +28,6 @@ Variants {
}
}
- Connections {
- target: ScalingService
- function onScaleChanged(screenName, scale) {
- if (screenName === modelData.name) {
- scaling = scale
- }
- }
- }
-
// Update dock apps when toplevels change
Connections {
target: ToplevelManager ? ToplevelManager.toplevels : null
@@ -71,14 +62,14 @@ Variants {
readonly property int showDelay: 100
readonly property int hideAnimationDuration: Style.animationFast
readonly property int showAnimationDuration: Style.animationFast
- readonly property int peekHeight: 1 // no scaling for peek
- readonly property int iconSize: 36 * scaling
- readonly property int floatingMargin: Settings.data.dock.floatingRatio * Style.marginL * scaling
+ readonly property int peekHeight: 1
+ readonly property int iconSize: 36
+ readonly property int floatingMargin: Settings.data.dock.floatingRatio * Style.marginL
// Bar detection and positioning properties
readonly property bool hasBar: modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false
readonly property bool barAtBottom: hasBar && Settings.data.bar.position === "bottom"
- readonly property int barHeight: Style.barHeight * scaling
+ readonly property int barHeight: Style.barHeight
// Shared state between windows
property bool dockHovered: false
@@ -264,7 +255,7 @@ Variants {
margins.bottom: {
switch (Settings.data.bar.position) {
case "bottom":
- return (Style.barHeight + Style.marginM) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling + floatingMargin : floatingMargin)
+ return (Style.barHeight + Style.marginM) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL + floatingMargin : floatingMargin)
default:
return floatingMargin
}
@@ -305,12 +296,12 @@ Variants {
Rectangle {
id: dockContainer
- width: dockLayout.implicitWidth + Style.marginM * scaling * 2
+ width: dockLayout.implicitWidth + Style.marginM * 2
height: Math.round(iconSize * 1.5)
color: Qt.alpha(Color.mSurface, Settings.data.dock.backgroundOpacity)
anchors.centerIn: parent
- radius: Style.radiusL * scaling
- border.width: Math.max(1, Style.borderS * scaling)
+ radius: Style.radiusL
+ border.width: Math.max(1, Style.borderS)
border.color: Qt.alpha(Color.mOutline, Settings.data.dock.backgroundOpacity)
MouseArea {
@@ -343,7 +334,7 @@ Variants {
Item {
id: dock
width: dockLayout.implicitWidth
- height: parent.height - (Style.marginM * 2 * scaling)
+ height: parent.height - (Style.marginM * 2)
anchors.centerIn: parent
function getAppIcon(appData): string {
@@ -354,7 +345,7 @@ Variants {
RowLayout {
id: dockLayout
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
Layout.preferredHeight: parent.height
anchors.centerIn: parent
@@ -401,6 +392,15 @@ Variants {
scale: appButton.hovered ? 1.15 : 1.0
+ // Apply dock-specific colorization shader only to non-focused apps
+ layer.enabled: !appButton.isActive && Settings.data.dock.colorizeIcons
+ layer.effect: ShaderEffect {
+ property color targetColor: Color.mOnSurface
+ property real colorizeMode: 0.0 // Dock mode (grayscale)
+
+ fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb")
+ }
+
Behavior on scale {
NumberAnimation {
duration: Style.animationNormal
@@ -446,7 +446,6 @@ Variants {
// Context menu popup
DockMenu {
id: contextMenu
- scaling: root.scaling
onHoveredChanged: menuHovered = hovered
onRequestClose: {
contextMenu.hide()
@@ -482,7 +481,9 @@ Variants {
anyAppHovered = true
const appName = appButton.appTitle || appButton.appId || "Unknown"
const tooltipText = appName.length > 40 ? appName.substring(0, 37) + "..." : appName
- TooltipService.show(Screen, appButton, tooltipText, "top")
+ if (!contextMenu.visible) {
+ TooltipService.show(Screen, appButton, tooltipText, "top")
+ }
if (autoHide) {
showTimer.stop()
hideTimer.stop()
@@ -508,7 +509,7 @@ Variants {
// Close any other existing context menu first
root.closeAllContextMenus()
// Hide tooltip when showing context menu
- TooltipService.hide()
+ TooltipService.hideImmediately()
contextMenu.show(appButton, modelData.toplevel || modelData)
return
}
@@ -540,7 +541,7 @@ Variants {
width: iconSize * 0.2
height: iconSize * 0.1
color: Color.mPrimary
- radius: Style.radiusXS * scaling
+ radius: Style.radiusXS
anchors.top: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
diff --git a/Modules/Dock/DockMenu.qml b/Modules/Dock/DockMenu.qml
index 30de31e4..d09ea6e7 100644
--- a/Modules/Dock/DockMenu.qml
+++ b/Modules/Dock/DockMenu.qml
@@ -13,20 +13,81 @@ PopupWindow {
property var toplevel: null
property Item anchorItem: null
- property real scaling: 1.0
+
property bool hovered: menuMouseArea.containsMouse
property var onAppClosed: null // Callback function for when an app is closed
+ property bool canAutoClose: false
// Track which menu item is hovered
- property int hoveredItem: -1 // -1: none, 0: focus, 1: pin, 2: close
+ property int hoveredItem: -1 // -1: none, otherwise the index of the item in `items`
+
+ property var items: []
signal requestClose
- implicitWidth: 140 * scaling
- implicitHeight: contextMenuColumn.implicitHeight + (Style.marginM * scaling * 2)
+ implicitWidth: Math.max(160, contextMenuColumn.implicitWidth)
+ implicitHeight: contextMenuColumn.implicitHeight + (Style.marginM * 2)
color: Color.transparent
visible: false
+ function initItems() {
+ // Is this a running app?
+ const isRunning = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel)
+
+ // Is this a pinned app?
+ const isPinned = root.toplevel && root.isAppPinned(root.toplevel.appId)
+
+ var next = []
+ if (isRunning) {
+ // Focus item
+ next.push({
+ "icon": "eye",
+ "text": I18n.tr("dock.menu.focus"),
+ "action": function () {
+ handleFocus()
+ }
+ })
+ }
+
+ // Pin/Unpin item
+ next.push({
+ "icon": !isPinned ? "pin" : "unpin",
+ "text": !isPinned ? I18n.tr("dock.menu.pin") : I18n.tr("dock.menu.unpin"),
+ "action": function () {
+ handlePin()
+ }
+ })
+
+ if (isRunning) {
+ // Close item
+ next.push({
+ "icon": "close",
+ "text": I18n.tr("dock.menu.close"),
+ "action": function () {
+ handleClose()
+ }
+ })
+ }
+
+ // Create a menu entry for each app-specific action definied in its .desktop file
+ if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId) {
+ const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId)
+ if (entry != null) {
+ entry.actions.forEach(function (action) {
+ next.push({
+ "icon": "",
+ "text": action.name,
+ "action": function () {
+ action.execute()
+ }
+ })
+ })
+ }
+ }
+
+ root.items = next
+ }
+
// Helper functions for pin/unpin functionality
function isAppPinned(appId) {
if (!appId)
@@ -56,7 +117,7 @@ PopupWindow {
anchor.item: anchorItem
anchor.rect.x: anchorItem ? (anchorItem.width - implicitWidth) / 2 : 0
- anchor.rect.y: anchorItem ? -implicitHeight - (Style.marginM * scaling) : 0
+ anchor.rect.y: anchorItem ? -implicitHeight - (Style.marginM) : 0
function show(item, toplevelData) {
if (!item) {
@@ -66,60 +127,71 @@ PopupWindow {
anchorItem = item
toplevel = toplevelData
+ initItems()
visible = true
+ canAutoClose = false
+ gracePeriodTimer.restart()
}
function hide() {
visible = false
+ root.items.length = 0
}
// Helper function to determine which menu item is under the mouse
function getHoveredItem(mouseY) {
- const itemHeight = 32 * scaling
- const startY = Style.marginM * scaling
+ const itemHeight = 32
+ const startY = Style.marginM
const relativeY = mouseY - startY
if (relativeY < 0)
return -1
const itemIndex = Math.floor(relativeY / itemHeight)
- return itemIndex >= 0 && itemIndex < 3 ? itemIndex : -1
+ return itemIndex >= 0 && itemIndex < root.items.length ? itemIndex : -1
}
- // Handle menu item clicks
- function handleItemClick(itemIndex) {
- switch (itemIndex) {
- case 0:
- // Focus
- if (root.toplevel?.activate) {
- root.toplevel.activate()
- }
- root.requestClose()
- break
- case 1:
- // Pin/Unpin
- if (root.toplevel?.appId) {
- root.toggleAppPin(root.toplevel.appId)
- }
- root.requestClose()
- break
- case 2:
- // Close
- // Check if toplevel is still valid before trying to close it
- const isValidToplevel = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel)
+ function handleFocus() {
+ if (root.toplevel?.activate) {
+ root.toplevel.activate()
+ }
+ root.requestClose()
+ }
- if (isValidToplevel && root.toplevel.close) {
- root.toplevel.close()
- // Trigger immediate dock update callback if provided
- if (root.onAppClosed && typeof root.onAppClosed === "function") {
- Qt.callLater(root.onAppClosed)
- }
- } else {
- Logger.warn("DockMenu", "Cannot close app - invalid toplevel reference")
+ function handlePin() {
+ if (root.toplevel?.appId) {
+ root.toggleAppPin(root.toplevel.appId)
+ }
+ root.requestClose()
+ }
+
+ function handleClose() {
+ // Check if toplevel is still valid before trying to close it
+ const isValidToplevel = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel)
+
+ if (isValidToplevel && root.toplevel.close) {
+ root.toplevel.close()
+ // Trigger immediate dock update callback if provided
+ if (root.onAppClosed && typeof root.onAppClosed === "function") {
+ Qt.callLater(root.onAppClosed)
+ }
+ } else {
+ Logger.warn("DockMenu", "Cannot close app - invalid toplevel reference")
+ }
+ root.hide()
+ root.requestClose()
+ }
+
+ // Short delay to ignore spurious events
+ Timer {
+ id: gracePeriodTimer
+ interval: 1500
+ repeat: false
+ onTriggered: {
+ root.canAutoClose = true
+ if (!menuMouseArea.containsMouse) {
+ closeTimer.start()
}
- root.hide()
- root.requestClose()
- break
}
}
@@ -136,9 +208,9 @@ PopupWindow {
Rectangle {
anchors.fill: parent
color: Color.mSurfaceVariant
- radius: Style.radiusS * scaling
+ radius: Style.radiusS
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
// Single MouseArea to handle both auto-close and menu interactions
MouseArea {
@@ -153,7 +225,10 @@ PopupWindow {
onExited: {
root.hoveredItem = -1
- closeTimer.start()
+ if (root.canAutoClose) {
+ // Only close if grace period has passed
+ closeTimer.start()
+ }
}
onPositionChanged: mouse => {
@@ -163,7 +238,7 @@ PopupWindow {
onClicked: mouse => {
const clickedItem = root.getHoveredItem(mouse.y)
if (clickedItem >= 0) {
- root.handleItemClick(clickedItem)
+ root.items[clickedItem].action.call()
}
}
}
@@ -171,100 +246,38 @@ PopupWindow {
ColumnLayout {
id: contextMenuColumn
anchors.fill: parent
- anchors.margins: Style.marginM * scaling
+ anchors.margins: Style.marginM
spacing: 0
- // Focus item
- Rectangle {
- Layout.fillWidth: true
- height: 32 * scaling
- color: root.hoveredItem === 0 ? Color.mTertiary : Color.transparent
- radius: Style.radiusXS * scaling
+ Repeater {
+ model: root.items
- RowLayout {
- anchors.left: parent.left
- anchors.leftMargin: Style.marginS * scaling
- anchors.verticalCenter: parent.verticalCenter
- spacing: Style.marginS * scaling
+ Rectangle {
+ Layout.fillWidth: true
+ height: 32
+ color: root.hoveredItem === index ? Color.mTertiary : Color.transparent
+ radius: Style.radiusXS
- NIcon {
- icon: "eye"
- pointSize: Style.fontSizeL * scaling
- color: root.hoveredItem === 0 ? Color.mOnTertiary : Color.mOnSurfaceVariant
- Layout.alignment: Qt.AlignVCenter
- }
+ RowLayout {
+ anchors.left: parent.left
+ anchors.leftMargin: Style.marginS
+ anchors.verticalCenter: parent.verticalCenter
+ spacing: Style.marginS
- NText {
- text: I18n.tr("dock.menu.focus")
- pointSize: Style.fontSizeS * scaling
- color: root.hoveredItem === 0 ? Color.mOnTertiary : Color.mOnSurfaceVariant
- Layout.alignment: Qt.AlignVCenter
- }
- }
- }
-
- // Pin/Unpin item
- Rectangle {
- Layout.fillWidth: true
- height: 32 * scaling
- color: root.hoveredItem === 1 ? Color.mTertiary : Color.transparent
- radius: Style.radiusXS * scaling
-
- RowLayout {
- anchors.left: parent.left
- anchors.leftMargin: Style.marginS * scaling
- anchors.verticalCenter: parent.verticalCenter
- spacing: Style.marginS * scaling
-
- NIcon {
- icon: {
- if (!root.toplevel)
- return "pin"
- return root.isAppPinned(root.toplevel.appId) ? "unpin" : "pin"
+ NIcon {
+ icon: modelData.icon
+ pointSize: Style.fontSizeL
+ color: root.hoveredItem === index ? Color.mOnTertiary : Color.mOnSurfaceVariant
+ Layout.alignment: Qt.AlignVCenter
}
- pointSize: Style.fontSizeL * scaling
- color: root.hoveredItem === 1 ? Color.mOnTertiary : Color.mOnSurfaceVariant
- Layout.alignment: Qt.AlignVCenter
- }
- NText {
- text: {
- if (!root.toplevel)
- return I18n.tr("dock.menu.pin")
- return root.isAppPinned(root.toplevel.appId) ? I18n.tr("dock.menu.unpin") : I18n.tr("dock.menu.pin")
+ NText {
+ text: modelData.text
+ pointSize: Style.fontSizeS
+ color: root.hoveredItem === index ? Color.mOnTertiary : Color.mOnSurfaceVariant
+ Layout.alignment: Qt.AlignVCenter
+ elide: Text.ElideRight
}
- pointSize: Style.fontSizeS * scaling
- color: root.hoveredItem === 1 ? Color.mOnTertiary : Color.mOnSurfaceVariant
- Layout.alignment: Qt.AlignVCenter
- }
- }
- }
-
- // Close item
- Rectangle {
- Layout.fillWidth: true
- height: 32 * scaling
- color: root.hoveredItem === 2 ? Color.mTertiary : Color.transparent
- radius: Style.radiusXS * scaling
-
- RowLayout {
- anchors.left: parent.left
- anchors.leftMargin: Style.marginS * scaling
- anchors.verticalCenter: parent.verticalCenter
- spacing: Style.marginS * scaling
-
- NIcon {
- icon: "close"
- pointSize: Style.fontSizeL * scaling
- color: root.hoveredItem === 2 ? Color.mOnTertiary : Color.mOnSurfaceVariant
- Layout.alignment: Qt.AlignVCenter
- }
-
- NText {
- text: I18n.tr("dock.menu.close")
- pointSize: Style.fontSizeS * scaling
- color: root.hoveredItem === 2 ? Color.mOnTertiary : Color.mOnSurfaceVariant
- Layout.alignment: Qt.AlignVCenter
}
}
}
diff --git a/Modules/Launcher/Launcher.qml b/Modules/Launcher/Launcher.qml
index 25bafe37..271d4c4b 100644
--- a/Modules/Launcher/Launcher.qml
+++ b/Modules/Launcher/Launcher.qml
@@ -35,9 +35,10 @@ NPanel {
property var plugins: []
property var activePlugin: null
property bool resultsReady: false
+ property bool ignoreMouseHover: false
- readonly property int badgeSize: Math.round(Style.baseWidgetSize * 1.6 * scaling)
- readonly property int entryHeight: Math.round(badgeSize + Style.marginM * 2 * scaling)
+ readonly property int badgeSize: Math.round(Style.baseWidgetSize * 1.6)
+ readonly property int entryHeight: Math.round(badgeSize + Style.marginM * 2)
// Public API for plugins
function setSearchText(text) {
@@ -94,6 +95,7 @@ NPanel {
// Lifecycle
onOpened: {
resultsReady = false
+ ignoreMouseHover = true
// Notify plugins
for (let plugin of plugins) {
@@ -110,6 +112,7 @@ NPanel {
onClosed: {
// Reset search text
searchText = ""
+ ignoreMouseHover = true
// Notify plugins
for (let plugin of plugins) {
@@ -154,6 +157,47 @@ NPanel {
color: Color.transparent
opacity: resultsReady ? 1.0 : 0.0
+ // Global MouseArea to detect mouse movement
+ MouseArea {
+ id: mouseMovementDetector
+ anchors.fill: parent
+ z: -999
+ hoverEnabled: true
+ propagateComposedEvents: true
+ acceptedButtons: Qt.NoButton
+
+ property real lastX: 0
+ property real lastY: 0
+ property bool initialized: false
+
+ onPositionChanged: mouse => {
+ // Store initial position
+ if (!initialized) {
+ lastX = mouse.x
+ lastY = mouse.y
+ initialized = true
+ return
+ }
+
+ // Check if mouse actually moved
+ const deltaX = Math.abs(mouse.x - lastX)
+ const deltaY = Math.abs(mouse.y - lastY)
+ if (deltaX > 1 || deltaY > 1) {
+ root.ignoreMouseHover = false
+ lastX = mouse.x
+ lastY = mouse.y
+ }
+ }
+
+ // Reset when launcher opens
+ Connections {
+ target: root
+ function onOpened() {
+ mouseMovementDetector.initialized = false
+ }
+ }
+ }
+
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
@@ -249,14 +293,14 @@ NPanel {
ColumnLayout {
anchors.fill: parent
- anchors.margins: Style.marginL * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginL
+ spacing: Style.marginM
NTextInput {
id: searchInput
Layout.fillWidth: true
- fontSize: Style.fontSizeL * scaling
+ fontSize: Style.fontSizeL
fontWeight: Style.fontWeightSemiBold
text: searchText
@@ -304,7 +348,7 @@ NPanel {
Layout.fillWidth: true
Layout.fillHeight: true
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
model: results
currentIndex: selectedIndex
cacheBuffer: resultsList.height * 2
@@ -321,7 +365,7 @@ NPanel {
delegate: Rectangle {
id: entry
- property bool isSelected: mouseArea.containsMouse || (index === selectedIndex)
+ property bool isSelected: (!root.ignoreMouseHover && mouseArea.containsMouse) || (index === selectedIndex)
// Accessor for app id
property string appId: (modelData && modelData.appId) ? String(modelData.appId) : ""
@@ -355,9 +399,9 @@ NPanel {
}
}
- width: resultsList.width - Style.marginS * scaling
+ width: resultsList.width - Style.marginS
implicitHeight: entryHeight
- radius: Style.radiusM * scaling
+ radius: Style.radiusM
color: entry.isSelected ? Color.mTertiary : Color.mSurface
Behavior on color {
@@ -370,19 +414,19 @@ NPanel {
ColumnLayout {
id: contentLayout
anchors.fill: parent
- anchors.margins: Style.marginM * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginM
+ spacing: Style.marginM
// Top row - Main entry content with pin button
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Icon badge or Image preview
Rectangle {
Layout.preferredWidth: badgeSize
Layout.preferredHeight: badgeSize
- radius: Style.radiusM * scaling
+ radius: Style.radiusM
color: Color.mSurfaceVariant
// Image preview for clipboard images
@@ -390,7 +434,7 @@ NPanel {
id: imagePreview
anchors.fill: parent
visible: modelData.isImage
- imageRadius: Style.radiusM * scaling
+ imageRadius: Style.radiusM
// This property creates a dependency on the service's revision counter
readonly property int _rev: ClipboardService.revision
@@ -411,7 +455,7 @@ NPanel {
BusyIndicator {
anchors.centerIn: parent
running: true
- width: Style.baseWidgetSize * 0.5 * scaling
+ width: Style.baseWidgetSize * 0.5
height: width
}
}
@@ -429,7 +473,7 @@ NPanel {
Loader {
id: iconLoader
anchors.fill: parent
- anchors.margins: Style.marginXS * scaling
+ anchors.margins: Style.marginXS
visible: !modelData.isImage || imagePreview.status === Image.Error
active: visible
@@ -449,7 +493,7 @@ NPanel {
anchors.centerIn: parent
visible: !imagePreview.visible && !iconLoader.visible
text: modelData.name ? modelData.name.charAt(0).toUpperCase() : "?"
- pointSize: Style.fontSizeXXL * scaling
+ pointSize: Style.fontSizeXXL
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
}
@@ -459,10 +503,10 @@ NPanel {
visible: modelData.isImage && imagePreview.visible
anchors.bottom: parent.bottom
anchors.right: parent.right
- anchors.margins: 2 * scaling
- width: formatLabel.width + 6 * scaling
- height: formatLabel.height + 2 * scaling
- radius: Style.radiusM * scaling
+ anchors.margins: 2
+ width: formatLabel.width + 6
+ height: formatLabel.height + 2
+ radius: Style.radiusM
color: Color.mSurfaceVariant
NText {
@@ -475,7 +519,7 @@ NPanel {
const parts = desc.split(" • ")
return parts[0] || "IMG"
}
- pointSize: Style.fontSizeXXS * scaling
+ pointSize: Style.fontSizeXXS
color: Color.mPrimary
}
}
@@ -484,11 +528,11 @@ NPanel {
// Text content
ColumnLayout {
Layout.fillWidth: true
- spacing: 0 * scaling
+ spacing: 0
NText {
text: modelData.name || "Unknown"
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: entry.isSelected ? Color.mOnTertiary : Color.mOnSurface
elide: Text.ElideRight
@@ -497,7 +541,7 @@ NPanel {
NText {
text: modelData.description || ""
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
color: entry.isSelected ? Color.mOnTertiary : Color.mOnSurfaceVariant
elide: Text.ElideRight
Layout.fillWidth: true
@@ -523,7 +567,9 @@ NPanel {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: {
- selectedIndex = index
+ if (!root.ignoreMouseHover) {
+ selectedIndex = index
+ }
}
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
@@ -550,7 +596,7 @@ NPanel {
const prefix = activePlugin?.name ? `${activePlugin.name}: ` : ""
return prefix + `${results.length} result${results.length !== 1 ? 's' : ''}`
}
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignCenter
}
diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml
index 60ba9f01..8b646e57 100644
--- a/Modules/LockScreen/LockScreen.qml
+++ b/Modules/LockScreen/LockScreen.qml
@@ -24,14 +24,6 @@ Loader {
onTriggered: lockScreen.active = false
}
- function formatTime() {
- return Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "h:mm A") : Qt.locale().toString(new Date(), "HH:mm")
- }
-
- function formatDate() {
- return Qt.locale().toString(new Date(), "dddd, MMMM d")
- }
-
function scheduleUnloadAfterUnlock() {
unloadAfterUnlockTimer.start()
}
@@ -57,7 +49,7 @@ Loader {
locked: lockScreen.active
WlSessionLockSurface {
- readonly property real scaling: ScalingService.dynamicScale(screen)
+ readonly property var now: Time.date
Item {
id: batteryIndicator
@@ -111,15 +103,13 @@ Loader {
visible: Settings.data.general.showScreenCorners
property color cornerColor: Settings.data.general.forceBlackScreenCorners ? Qt.rgba(0, 0, 0, 1) : Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
- property real cornerRadius: Style.screenRadius * scaling
- property real cornerSize: Style.screenRadius * scaling
- property real barHeight: Style.barHeight * scaling
+ property real cornerRadius: Style.screenRadius
+ property real cornerSize: Style.screenRadius
// Top-left concave corner
Canvas {
anchors.top: parent.top
anchors.left: parent.left
- anchors.topMargin: Settings.data.bar.position === "top" ? barHeight : 0
width: parent.cornerSize
height: parent.cornerSize
antialiasing: true
@@ -154,7 +144,6 @@ Loader {
Canvas {
anchors.top: parent.top
anchors.right: parent.right
- anchors.topMargin: Settings.data.bar.position === "top" ? barHeight : 0
width: parent.cornerSize
height: parent.cornerSize
antialiasing: true
@@ -189,7 +178,6 @@ Loader {
Canvas {
anchors.bottom: parent.bottom
anchors.left: parent.left
- anchors.bottomMargin: Settings.data.bar.position === "bottom" ? barHeight : 0
width: parent.cornerSize
height: parent.cornerSize
antialiasing: true
@@ -224,7 +212,6 @@ Loader {
Canvas {
anchors.bottom: parent.bottom
anchors.right: parent.right
- anchors.bottomMargin: Settings.data.bar.position === "bottom" ? barHeight : 0
width: parent.cornerSize
height: parent.cornerSize
antialiasing: true
@@ -259,137 +246,192 @@ Loader {
Item {
anchors.fill: parent
- // Time and Date
- ColumnLayout {
+ // Time, Date, and User Profile Container
+ Rectangle {
+ width: Math.max(500, contentRow.implicitWidth + 32)
+ height: Math.max(120, contentRow.implicitHeight + 32)
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
- anchors.topMargin: 100 * scaling
- spacing: 12 * scaling
+ anchors.topMargin: 100
+ radius: Style.radiusL
+ color: Color.mSurface
+ border.color: Qt.alpha(Color.mOutline, 0.2)
+ border.width: 1
- NText {
- id: timeText
- text: lockScreen.formatTime()
- Layout.alignment: Qt.AlignHCenter
- pointSize: 72 * scaling
- font.weight: Font.Medium
- color: Color.mOnSurface
- horizontalAlignment: Text.AlignHCenter
- opacity: 0.95
-
- SequentialAnimation on opacity {
- loops: Animation.Infinite
- NumberAnimation {
- to: 0.7
- duration: 3000
- easing.type: Easing.InOutQuad
- }
- NumberAnimation {
- to: 0.95
- duration: 3000
- easing.type: Easing.InOutQuad
- }
- }
- }
-
- NText {
- id: dateText
- text: lockScreen.formatDate()
- Layout.alignment: Qt.AlignHCenter
- pointSize: Style.fontSizeXL * scaling
- font.weight: Font.Medium
- color: Color.mOnSurface
- horizontalAlignment: Text.AlignHCenter
- opacity: 0.9
- }
- }
-
- // User Profile
- ColumnLayout {
- anchors.centerIn: parent
- spacing: 10 * scaling
- Layout.alignment: Qt.AlignHCenter
-
- Rectangle {
- Layout.preferredWidth: 140 * scaling
- Layout.preferredHeight: 140 * scaling
- Layout.alignment: Qt.AlignHCenter
- radius: width * 0.5
- color: Color.transparent
+ RowLayout {
+ id: contentRow
+ anchors.fill: parent
+ anchors.margins: 16
+ spacing: 32
+ // Left side: Avatar
Rectangle {
- anchors.fill: parent
- radius: parent.radius
+ Layout.preferredWidth: 70
+ Layout.preferredHeight: 70
+ Layout.alignment: Qt.AlignVCenter
+ radius: width * 0.5
color: Color.transparent
- border.color: Qt.alpha(Color.mPrimary, 0.8)
- border.width: 3
- SequentialAnimation on border.color {
- loops: Animation.Infinite
- ColorAnimation {
- to: Qt.alpha(Color.mPrimary, 1.0)
- duration: 2000
- easing.type: Easing.InOutQuad
+ Rectangle {
+ anchors.fill: parent
+ radius: parent.radius
+ color: Color.transparent
+ border.color: Qt.alpha(Color.mPrimary, 0.8)
+ border.width: 2
+
+ SequentialAnimation on border.color {
+ loops: Animation.Infinite
+ ColorAnimation {
+ to: Qt.alpha(Color.mPrimary, 1.0)
+ duration: 2000
+ easing.type: Easing.InOutQuad
+ }
+ ColorAnimation {
+ to: Qt.alpha(Color.mPrimary, 0.8)
+ duration: 2000
+ easing.type: Easing.InOutQuad
+ }
}
- ColorAnimation {
- to: Qt.alpha(Color.mPrimary, 0.8)
- duration: 2000
- easing.type: Easing.InOutQuad
+ }
+
+ NImageCircled {
+ anchors.centerIn: parent
+ width: 66
+ height: 66
+ imagePath: Settings.preprocessPath(Settings.data.general.avatarImage)
+ fallbackIcon: "person"
+
+ SequentialAnimation on scale {
+ loops: Animation.Infinite
+ NumberAnimation {
+ to: 1.02
+ duration: 4000
+ easing.type: Easing.InOutQuad
+ }
+ NumberAnimation {
+ to: 1.0
+ duration: 4000
+ easing.type: Easing.InOutQuad
+ }
}
}
}
- NImageCircled {
- anchors.centerIn: parent
- width: 130 * scaling
- height: 130 * scaling
- imagePath: Settings.data.general.avatarImage
- fallbackIcon: "person"
+ // Center: User Info Column (left-aligned text)
+ ColumnLayout {
+ Layout.alignment: Qt.AlignVCenter
+ spacing: 2
- SequentialAnimation on scale {
- loops: Animation.Infinite
- NumberAnimation {
- to: 1.02
- duration: 4000
- easing.type: Easing.InOutQuad
+ // Welcome back + Username on one line
+ NText {
+ text: I18n.tr("lock-screen.welcome-back") + " " + (Quickshell.env("USER").charAt(0).toUpperCase() + Quickshell.env("USER").slice(1)) + "!"
+ pointSize: Style.fontSizeXXL
+ font.weight: Font.Medium
+ color: Color.mOnSurface
+ horizontalAlignment: Text.AlignLeft
+ }
+
+ // Date below
+ NText {
+ text: Qt.locale().toString(Time.date, "dddd, MMMM d")
+ pointSize: Style.fontSizeXL
+ font.weight: Font.Medium
+ color: Color.mOnSurfaceVariant
+ horizontalAlignment: Text.AlignLeft
+ }
+ }
+
+ // Spacer to push time to the right
+ Item {
+ Layout.fillWidth: true
+ }
+
+ Item {
+ Layout.preferredWidth: 70
+ Layout.preferredHeight: 70
+ Layout.alignment: Qt.AlignVCenter
+
+ // Seconds circular progress
+ Canvas {
+ id: secondsProgress
+ anchors.fill: parent
+
+ property real progress: Time.date.getSeconds() / 60
+ onProgressChanged: requestPaint()
+
+ Connections {
+ target: Time
+ function onDateChanged() {
+ const total = Time.date.getSeconds() * 1000 + Time.date.getMilliseconds()
+ secondsProgress.progress = total / 60000
+ }
}
- NumberAnimation {
- to: 1.0
- duration: 4000
- easing.type: Easing.InOutQuad
+
+ onPaint: {
+ var ctx = getContext("2d")
+ var centerX = width / 2
+ var centerY = height / 2
+ var radius = Math.min(width, height) / 2 - 3
+
+ ctx.reset()
+
+ // Background circle
+ ctx.beginPath()
+ ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI)
+ ctx.lineWidth = 2.5
+ ctx.strokeStyle = Qt.alpha(Color.mOnSurface, 0.15)
+ ctx.stroke()
+
+ // Progress arc
+ ctx.beginPath()
+ ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI)
+ ctx.lineWidth = 2.5
+ ctx.strokeStyle = Color.mPrimary
+ ctx.lineCap = "round"
+ ctx.stroke()
+ }
+ }
+
+ // Digital clock
+ ColumnLayout {
+ anchors.centerIn: parent
+ spacing: 0
+
+ NText {
+ text: {
+ var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(Time.date, "hh AP") : Qt.locale().toString(Time.date, "HH")
+ return t
+ }
+ pointSize: Style.fontSizeM
+ font.weight: Style.fontWeightBold
+ family: Settings.data.ui.fontFixed
+ color: Color.mOnSurface
+ horizontalAlignment: Text.AlignHCenter
+ Layout.alignment: Qt.AlignHCenter
+ }
+
+ NText {
+ text: Qt.formatTime(Time.date, "mm")
+ pointSize: Style.fontSizeM
+ font.weight: Style.fontWeightBold
+ family: Settings.data.ui.fontFixed
+ color: Color.mOnSurfaceVariant
+ horizontalAlignment: Text.AlignHCenter
+ Layout.alignment: Qt.AlignHCenter
}
}
}
}
-
- NText {
- text: I18n.tr("lock-screen.welcome-back")
- Layout.alignment: Qt.AlignHCenter
- pointSize: Style.fontSizeXL * scaling
- font.weight: Font.Medium
- color: Color.mOnSurface
- horizontalAlignment: Text.AlignHCenter
- opacity: 0.9
- }
-
- NText {
- text: Quickshell.env("USER")
- Layout.alignment: Qt.AlignHCenter
- pointSize: Style.fontSizeXXXL * scaling
- font.weight: Font.Medium
- color: Color.mOnSurface
- horizontalAlignment: Text.AlignHCenter
- opacity: 0.9
- }
}
// Error notification
Rectangle {
- width: 450 * scaling
- height: 60 * scaling
+ width: 450
+ height: 60
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
- anchors.bottomMargin: 300 * scaling
- radius: 30 * scaling
+ anchors.bottomMargin: (Settings.data.general.compactLockScreen ? 240 : 320) * Style.uiScaleRatio
+ radius: 30
color: Color.mError
border.color: Color.mError
border.width: 1
@@ -398,18 +440,18 @@ Loader {
RowLayout {
anchors.centerIn: parent
- spacing: 10 * scaling
+ spacing: 10
NIcon {
icon: "alert-circle"
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
color: Color.mOnError
}
NText {
text: lockContext.errorMessage || "Authentication failed"
color: Color.mOnError
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
font.weight: Font.Medium
horizontalAlignment: Text.AlignHCenter
}
@@ -423,42 +465,108 @@ Loader {
}
}
- // Bottom container with weather, password input and controls
+ // Compact status indicators container (compact mode only)
Rectangle {
- width: 750 * scaling
- height: 220 * scaling
+ width: {
+ var hasBattery = UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent
+ var hasKeyboard = keyboardLayout.currentLayout !== "Unknown"
+
+ if (hasBattery && hasKeyboard) {
+ return 200
+ } else if (hasBattery || hasKeyboard) {
+ return 120
+ } else {
+ return 0
+ }
+ }
+ height: 40
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
- anchors.bottomMargin: 100 * scaling
- radius: 32 * scaling
+ anchors.bottomMargin: 96 + (Settings.data.general.compactLockScreen ? 116 : 220)
+ topLeftRadius: Style.radiusL
+ topRightRadius: Style.radiusL
+ color: Color.mSurface
+ visible: Settings.data.general.compactLockScreen && ((UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent) || keyboardLayout.currentLayout !== "Unknown")
+
+ RowLayout {
+ anchors.centerIn: parent
+ spacing: 16
+
+ // Battery indicator
+ RowLayout {
+ spacing: 6
+ visible: UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent
+
+ NIcon {
+ icon: BatteryService.getIcon(Math.round(UPower.displayDevice.percentage * 100), UPower.displayDevice.state === UPowerDeviceState.Charging, true)
+ pointSize: Style.fontSizeM
+ color: UPower.displayDevice.state === UPowerDeviceState.Charging ? Color.mPrimary : Color.mOnSurfaceVariant
+ }
+
+ NText {
+ text: Math.round(UPower.displayDevice.percentage * 100) + "%"
+ color: Color.mOnSurfaceVariant
+ pointSize: Style.fontSizeM
+ font.weight: Font.Medium
+ }
+ }
+
+ // Keyboard layout indicator
+ RowLayout {
+ spacing: 6
+ visible: keyboardLayout.currentLayout !== "Unknown"
+
+ NIcon {
+ icon: "keyboard"
+ pointSize: Style.fontSizeM
+ color: Color.mOnSurfaceVariant
+ }
+
+ NText {
+ text: keyboardLayout.currentLayout
+ color: Color.mOnSurfaceVariant
+ pointSize: Style.fontSizeM
+ font.weight: Font.Medium
+ elide: Text.ElideRight
+ }
+ }
+ }
+ }
+
+ // Bottom container with weather, password input and controls
+ Rectangle {
+ width: 750
+ height: Settings.data.general.compactLockScreen ? 120 : 220
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 100
+ radius: Style.radiusL
color: Color.mSurface
- border.color: Qt.alpha(Color.mOutline, 0.2)
- border.width: 1
ColumnLayout {
anchors.fill: parent
- anchors.margins: 14 * scaling
- spacing: 14 * scaling
+ anchors.margins: 14
+ spacing: 14
// Weather section
RowLayout {
Layout.fillWidth: true
- Layout.preferredHeight: 65 * scaling
- spacing: 18 * scaling
- visible: LocationService.coordinatesReady && LocationService.data.weather !== null
+ Layout.preferredHeight: 65
+ spacing: 18
+ visible: !Settings.data.general.compactLockScreen && LocationService.coordinatesReady && LocationService.data.weather !== null
// Media widget with visualizer
Rectangle {
- Layout.preferredWidth: 220 * scaling
- Layout.preferredHeight: 50 * scaling
- radius: 25 * scaling
+ Layout.preferredWidth: 220
+ Layout.preferredHeight: 50
+ radius: 25
color: Color.transparent
clip: true
visible: MediaService.currentPlayer && MediaService.canPlay
Loader {
anchors.fill: parent
- anchors.margins: 4 * scaling
+ anchors.margins: 4
active: Settings.data.audio.visualizerType === "linear"
z: 0
sourceComponent: LinearSpectrum {
@@ -471,7 +579,7 @@ Loader {
Loader {
anchors.fill: parent
- anchors.margins: 4 * scaling
+ anchors.margins: 4
active: Settings.data.audio.visualizerType === "mirrored"
z: 0
sourceComponent: MirroredSpectrum {
@@ -484,7 +592,7 @@ Loader {
Loader {
anchors.fill: parent
- anchors.margins: 4 * scaling
+ anchors.margins: 4
active: Settings.data.audio.visualizerType === "wave"
z: 0
sourceComponent: WaveSpectrum {
@@ -497,35 +605,35 @@ Loader {
RowLayout {
anchors.fill: parent
- anchors.margins: 8 * scaling
- spacing: 8 * scaling
+ anchors.margins: 8
+ spacing: 8
z: 1
Rectangle {
- Layout.preferredWidth: 34 * scaling
- Layout.preferredHeight: 34 * scaling
+ Layout.preferredWidth: 34
+ Layout.preferredHeight: 34
radius: width * 0.5
color: Color.transparent
clip: true
NImageCircled {
anchors.fill: parent
- anchors.margins: 2 * scaling
+ anchors.margins: 2
imagePath: MediaService.trackArtUrl
fallbackIcon: "disc"
- fallbackIconSize: Style.fontSizeM * scaling
+ fallbackIconSize: Style.fontSizeM
borderColor: Color.mOutline
- borderWidth: Math.max(1, Style.borderS * scaling)
+ borderWidth: Math.max(1, Style.borderS)
}
}
ColumnLayout {
Layout.fillWidth: true
- spacing: 2 * scaling
+ spacing: 2
NText {
text: MediaService.trackTitle || "No media"
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
Layout.fillWidth: true
@@ -534,7 +642,7 @@ Loader {
NText {
text: MediaService.trackArtist || ""
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
elide: Text.ElideRight
@@ -546,46 +654,64 @@ Loader {
Rectangle {
Layout.preferredWidth: 1
Layout.fillHeight: true
- Layout.rightMargin: 4 * scaling
+ Layout.rightMargin: 4
color: Qt.alpha(Color.mOutline, 0.3)
visible: MediaService.currentPlayer && MediaService.canPlay
}
Item {
- Layout.preferredWidth: Style.marginM * scaling
+ Layout.preferredWidth: Style.marginM
visible: !(MediaService.currentPlayer && MediaService.canPlay)
}
// Current weather
RowLayout {
- Layout.preferredWidth: 180 * scaling
- spacing: 8 * scaling
+ Layout.preferredWidth: 180
+ spacing: 8
NIcon {
Layout.alignment: Qt.AlignVCenter
icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode)
- pointSize: Style.fontSizeXXXL * scaling
+ pointSize: Style.fontSizeXXXL
color: Color.mPrimary
}
ColumnLayout {
Layout.fillWidth: true
- spacing: 2 * scaling
+ spacing: 2
RowLayout {
Layout.fillWidth: true
- spacing: 12 * scaling
+ spacing: 12
NText {
- text: Math.round(LocationService.data.weather.current_weather.temperature) + "°"
- pointSize: Style.fontSizeXL * scaling
+ text: {
+ var temp = LocationService.data.weather.current_weather.temperature
+ var suffix = "C"
+ if (Settings.data.location.useFahrenheit) {
+ temp = LocationService.celsiusToFahrenheit(temp)
+ suffix = "F"
+ }
+ temp = Math.round(temp)
+ return temp + "°" + suffix
+ }
+ pointSize: Style.fontSizeXL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
- text: LocationService.data.weather.current_weather.windspeed + " km/h"
- pointSize: Style.fontSizeM * scaling
+ text: {
+ var wind = LocationService.data.weather.current_weather.windspeed
+ var unit = "km/h"
+ if (Settings.data.location.useFahrenheit) {
+ wind = wind * 0.621371 // Convert km/h to mph
+ unit = "mph"
+ }
+ wind = Math.round(wind)
+ return wind + " " + unit
+ }
+ pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
font.weight: Font.Normal
}
@@ -593,17 +719,17 @@ Loader {
RowLayout {
Layout.fillWidth: true
- spacing: 8 * scaling
+ spacing: 8
NText {
text: Settings.data.location.name.split(",")[0]
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
}
NText {
text: (LocationService.data.weather.current && LocationService.data.weather.current.relativehumidity_2m) ? LocationService.data.weather.current.relativehumidity_2m + "% humidity" : ""
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
}
}
@@ -612,19 +738,19 @@ Loader {
// 3-day forecast
RowLayout {
- Layout.preferredWidth: 260 * scaling
- Layout.rightMargin: 8 * scaling
- spacing: 4 * scaling
+ Layout.preferredWidth: 260
+ Layout.rightMargin: 8
+ spacing: 4
Repeater {
model: 3
delegate: ColumnLayout {
Layout.fillWidth: true
- spacing: 3 * scaling
+ spacing: 3
NText {
- text: Qt.locale().toString(new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/")), "ddd")
- pointSize: Style.fontSizeM * scaling
+ text: Qt.locale().toString(now, "ddd")
+ pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
@@ -633,13 +759,23 @@ Loader {
NIcon {
Layout.alignment: Qt.AlignHCenter
icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index])
- pointSize: Style.fontSizeXL * scaling
+ pointSize: Style.fontSizeXL
color: Color.mOnSurfaceVariant
}
NText {
- text: Math.round(LocationService.data.weather.daily.temperature_2m_max[index]) + "°/" + Math.round(LocationService.data.weather.daily.temperature_2m_min[index]) + "°"
- pointSize: Style.fontSizeM * scaling
+ text: {
+ var max = LocationService.data.weather.daily.temperature_2m_max[index]
+ var min = LocationService.data.weather.daily.temperature_2m_min[index]
+ if (Settings.data.location.useFahrenheit) {
+ max = LocationService.celsiusToFahrenheit(max)
+ min = LocationService.celsiusToFahrenheit(min)
+ }
+ max = Math.round(max)
+ min = Math.round(min)
+ return max + "°/" + min + "°"
+ }
+ pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignHCenter
@@ -649,47 +785,50 @@ Loader {
}
}
- // Battery and Keyboard Layout
+ // Battery and Keyboard Layout (full mode only)
ColumnLayout {
- Layout.preferredWidth: 60 * scaling
- spacing: 4 * scaling
+ Layout.preferredWidth: 60
+ spacing: 8
+ // Battery
RowLayout {
- Layout.preferredWidth: 60 * scaling
- Layout.preferredHeight: 22 * scaling
- spacing: 4 * scaling
+ spacing: 4
visible: UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent
NIcon {
icon: BatteryService.getIcon(Math.round(UPower.displayDevice.percentage * 100), UPower.displayDevice.state === UPowerDeviceState.Charging, true)
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
color: UPower.displayDevice.state === UPowerDeviceState.Charging ? Color.mPrimary : Color.mOnSurfaceVariant
}
NText {
text: Math.round(UPower.displayDevice.percentage * 100) + "%"
color: Color.mOnSurfaceVariant
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
font.weight: Font.Medium
+ elide: Text.ElideRight
+ Layout.fillWidth: true
}
}
+ // Keyboard Layout
RowLayout {
- Layout.preferredWidth: 60 * scaling
- Layout.preferredHeight: 22 * scaling
- spacing: 4 * scaling
+ spacing: 4
+ visible: keyboardLayout.currentLayout !== "Unknown"
NIcon {
icon: "keyboard"
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
}
NText {
text: keyboardLayout.currentLayout
color: Color.mOnSurfaceVariant
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
font.weight: Font.Medium
+ elide: Text.ElideRight
+ Layout.fillWidth: true
}
}
}
@@ -701,26 +840,26 @@ Loader {
spacing: 0
Item {
- Layout.preferredWidth: Style.marginM * scaling
+ Layout.preferredWidth: Style.marginM
}
Rectangle {
Layout.fillWidth: true
- Layout.preferredHeight: 48 * scaling
- radius: 24 * scaling
+ Layout.preferredHeight: 48
+ radius: 24
color: Color.mSurface
border.color: passwordInput.activeFocus ? Color.mPrimary : Qt.alpha(Color.mOutline, 0.3)
border.width: passwordInput.activeFocus ? 2 : 1
Row {
anchors.left: parent.left
- anchors.leftMargin: 18 * scaling
+ anchors.leftMargin: 18
anchors.verticalCenter: parent.verticalCenter
- spacing: 14 * scaling
+ spacing: 14
NIcon {
icon: "lock"
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
color: passwordInput.activeFocus ? Color.mPrimary : Color.mOnSurfaceVariant
anchors.verticalCenter: parent.verticalCenter
}
@@ -732,7 +871,7 @@ Loader {
height: 0
visible: false
enabled: !lockContext.unlockInProgress
- font.pointSize: Style.fontSizeM * scaling
+ font.pointSize: Style.fontSizeM
color: Color.mPrimary
echoMode: TextInput.Password
passwordCharacter: "•"
@@ -753,8 +892,8 @@ Loader {
spacing: 0
Rectangle {
- width: 2 * scaling
- height: 20 * scaling
+ width: 2
+ height: 20
color: Color.mPrimary
visible: passwordInput.activeFocus && passwordInput.text.length === 0
anchors.verticalCenter: parent.verticalCenter
@@ -774,7 +913,7 @@ Loader {
}
Row {
- spacing: 6 * scaling
+ spacing: 6
visible: passwordInput.text.length > 0
anchors.verticalCenter: parent.verticalCenter
@@ -783,7 +922,7 @@ Loader {
NIcon {
icon: "circle-filled"
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
color: Color.mPrimary
opacity: 1.0
}
@@ -791,8 +930,8 @@ Loader {
}
Rectangle {
- width: 2 * scaling
- height: 20 * scaling
+ width: 2
+ height: 20
color: Color.mPrimary
visible: passwordInput.activeFocus && passwordInput.text.length > 0
anchors.verticalCenter: parent.verticalCenter
@@ -815,10 +954,10 @@ Loader {
Rectangle {
anchors.right: parent.right
- anchors.rightMargin: 8 * scaling
+ anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
- width: 36 * scaling
- height: 36 * scaling
+ width: 36
+ height: 36
radius: width * 0.5
color: submitButtonArea.containsMouse ? Color.mPrimary : Qt.alpha(Color.mPrimary, 0.8)
border.color: Color.mPrimary
@@ -828,7 +967,7 @@ Loader {
NIcon {
anchors.centerIn: parent
icon: "arrow-forward"
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
color: Color.mOnPrimary
}
@@ -849,40 +988,42 @@ Loader {
}
Item {
- Layout.preferredWidth: Style.marginM * scaling
+ Layout.preferredWidth: Style.marginM
}
}
// System control buttons
RowLayout {
Layout.fillWidth: true
- Layout.preferredHeight: 48 * scaling
- spacing: 10 * scaling
+ Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48
+ spacing: 10
Item {
- Layout.preferredWidth: Style.marginM * scaling
+ Layout.preferredWidth: Style.marginM
}
Rectangle {
Layout.fillWidth: true
- Layout.preferredHeight: 48 * scaling
- radius: 24 * scaling
+ Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48
+ radius: Settings.data.general.compactLockScreen ? 18 : 24
color: logoutButtonArea.containsMouse ? Color.mTertiary : "transparent"
+ border.color: Color.mOutline
+ border.width: 1
RowLayout {
anchors.centerIn: parent
- spacing: 6 * scaling
+ spacing: 6
NIcon {
icon: "logout"
- pointSize: Style.fontSizeL * scaling
+ pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL
color: logoutButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
}
NText {
text: I18n.tr("session-menu.logout")
color: logoutButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
- pointSize: Style.fontSizeM * scaling
+ pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM
font.weight: Font.Medium
}
}
@@ -900,28 +1041,85 @@ Loader {
easing.type: Easing.OutCubic
}
}
+
+ Behavior on border.color {
+ ColorAnimation {
+ duration: 200
+ easing.type: Easing.OutCubic
+ }
+ }
}
Rectangle {
Layout.fillWidth: true
- Layout.preferredHeight: 48 * scaling
- radius: 24 * scaling
- color: rebootButtonArea.containsMouse ? Color.mTertiary : "transparent"
+ Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48
+ radius: Settings.data.general.compactLockScreen ? 18 : 24
+ color: suspendButtonArea.containsMouse ? Color.mTertiary : "transparent"
+ border.color: Color.mOutline
+ border.width: 1
RowLayout {
anchors.centerIn: parent
- spacing: 6 * scaling
+ spacing: 6
+
+ NIcon {
+ icon: "suspend"
+ pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL
+ color: suspendButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
+ }
+
+ NText {
+ text: I18n.tr("session-menu.suspend")
+ color: suspendButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
+ pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM
+ font.weight: Font.Medium
+ }
+ }
+
+ MouseArea {
+ id: suspendButtonArea
+ anchors.fill: parent
+ hoverEnabled: true
+ onClicked: CompositorService.suspend()
+ }
+
+ Behavior on color {
+ ColorAnimation {
+ duration: 200
+ easing.type: Easing.OutCubic
+ }
+ }
+
+ Behavior on border.color {
+ ColorAnimation {
+ duration: 200
+ easing.type: Easing.OutCubic
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48
+ radius: Settings.data.general.compactLockScreen ? 18 : 24
+ color: rebootButtonArea.containsMouse ? Color.mTertiary : "transparent"
+ border.color: Color.mOutline
+ border.width: 1
+
+ RowLayout {
+ anchors.centerIn: parent
+ spacing: 6
NIcon {
icon: "reboot"
- pointSize: Style.fontSizeL * scaling
+ pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL
color: rebootButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
}
NText {
text: I18n.tr("session-menu.reboot")
color: rebootButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
- pointSize: Style.fontSizeM * scaling
+ pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM
font.weight: Font.Medium
}
}
@@ -939,30 +1137,37 @@ Loader {
easing.type: Easing.OutCubic
}
}
+
+ Behavior on border.color {
+ ColorAnimation {
+ duration: 200
+ easing.type: Easing.OutCubic
+ }
+ }
}
Rectangle {
Layout.fillWidth: true
- Layout.preferredHeight: 48 * scaling
- radius: 24 * scaling
+ Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48
+ radius: Settings.data.general.compactLockScreen ? 18 : 24
color: shutdownButtonArea.containsMouse ? Color.mError : "transparent"
- border.color: shutdownButtonArea.containsMouse ? Color.mError : Color.transparent
+ border.color: shutdownButtonArea.containsMouse ? Color.mError : Color.mOutline
border.width: 1
RowLayout {
anchors.centerIn: parent
- spacing: 6 * scaling
+ spacing: 6
NIcon {
icon: "shutdown"
- pointSize: Style.fontSizeL * scaling
+ pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL
color: shutdownButtonArea.containsMouse ? Color.mOnError : Color.mOnSurfaceVariant
}
NText {
text: I18n.tr("session-menu.shutdown")
color: shutdownButtonArea.containsMouse ? Color.mOnError : Color.mOnSurfaceVariant
- pointSize: Style.fontSizeM * scaling
+ pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM
font.weight: Font.Medium
}
}
@@ -990,7 +1195,7 @@ Loader {
}
Item {
- Layout.preferredWidth: Style.marginM * scaling
+ Layout.preferredWidth: Style.marginM
}
}
}
diff --git a/Modules/Notification/Notification.qml b/Modules/Notification/Notification.qml
index b6dd8672..3c97971a 100644
--- a/Modules/Notification/Notification.qml
+++ b/Modules/Notification/Notification.qml
@@ -17,7 +17,6 @@ Variants {
id: root
required property ShellScreen modelData
- property real scaling: ScalingService.getScreenScale(modelData)
// Access the notification model from the service
property ListModel notificationModel: NotificationService.activeList
@@ -42,15 +41,6 @@ Variants {
}
}
- Connections {
- target: ScalingService
- function onScaleChanged(screenName, scale) {
- if (root.modelData && screenName === root.modelData.name) {
- root.scaling = scale
- }
- }
- }
-
sourceComponent: PanelWindow {
screen: modelData
@@ -79,10 +69,10 @@ Variants {
margins.top: {
if (!(anchors.top))
return 0
- var base = Style.marginM * scaling
+ var base = Style.marginM
if (Settings.data.bar.position === "top") {
- var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
- return (Style.barHeight * scaling) + base + floatExtraV
+ var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0
+ return (Style.barHeight) + base + floatExtraV
}
return base
}
@@ -90,10 +80,10 @@ Variants {
margins.bottom: {
if (!(anchors.bottom))
return 0
- var base = Style.marginM * scaling
+ var base = Style.marginM
if (Settings.data.bar.position === "bottom") {
- var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
- return (Style.barHeight * scaling) + base + floatExtraV
+ var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0
+ return (Style.barHeight) + base + floatExtraV
}
return base
}
@@ -101,10 +91,10 @@ Variants {
margins.left: {
if (!(anchors.left))
return 0
- var base = Style.marginM * scaling
+ var base = Style.marginM
if (Settings.data.bar.position === "left") {
- var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
- return (Style.barHeight * scaling) + base + floatExtraH
+ var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
+ return (Style.barHeight) + base + floatExtraH
}
return base
}
@@ -112,15 +102,15 @@ Variants {
margins.right: {
if (!(anchors.right))
return 0
- var base = Style.marginM * scaling
+ var base = Style.marginM
if (Settings.data.bar.position === "right") {
- var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
- return (Style.barHeight * scaling) + base + floatExtraH
+ var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
+ return (Style.barHeight) + base + floatExtraH
}
return base
}
- implicitWidth: 360 * scaling
+ implicitWidth: 360
implicitHeight: notificationStack.implicitHeight
WlrLayershell.exclusionMode: ExclusionMode.Ignore
@@ -165,10 +155,20 @@ Variants {
anchors.left: parent.isLeft ? parent.left : undefined
anchors.right: parent.isRight ? parent.right : undefined
anchors.horizontalCenter: parent.isCentered ? parent.horizontalCenter : undefined
- spacing: Style.marginS * scaling
- width: 360 * scaling
+ spacing: Style.marginS
+ width: 360
visible: true
+ // Animate when notifications are added/removed
+ Behavior on implicitHeight {
+ SpringAnimation {
+ spring: 2.0
+ damping: 0.4
+ epsilon: 0.01
+ mass: 0.8
+ }
+ }
+
// Multiple notifications display
Repeater {
model: notificationModel
@@ -178,15 +178,14 @@ Variants {
// Store the notification ID and data for reference
property string notificationId: model.id
property var notificationData: model
- property real cardScaling: root.scaling
- Layout.preferredWidth: 360 * cardScaling
- Layout.preferredHeight: notificationLayout.implicitHeight + (Style.marginL * 2 * cardScaling)
+ Layout.preferredWidth: 360
+ Layout.preferredHeight: notificationLayout.implicitHeight + (Style.marginL * 2)
Layout.maximumHeight: Layout.preferredHeight
- radius: Style.radiusL * cardScaling
+ radius: Style.radiusL
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * cardScaling)
+ border.width: Math.max(1, Style.borderS)
color: Color.mSurface
// Optimized progress bar container
@@ -195,7 +194,7 @@ Variants {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
- height: 2 * cardScaling
+ height: 2
color: Color.transparent
// Pre-calculate available width for the progress bar
@@ -243,8 +242,12 @@ Variants {
// Animation properties
property real scaleValue: 0.8
property real opacityValue: 0.0
+ property real slideOffset: 0
property bool isRemoving: false
+ // Staggered animation delay based on index
+ readonly property int animationDelay: index * 100
+
// Right-click to dismiss
MouseArea {
anchors.fill: parent
@@ -256,14 +259,55 @@ Variants {
}
}
- // Scale and fade-in animation
+ // Scale, fade, and slide animation
scale: scaleValue
opacity: opacityValue
+ // Slide animation based on notification position (vertical only for stacking)
+ y: slideOffset
+
+ // Calculate slide direction based on notification location
+ readonly property real slideDistance: 300
+ readonly property real slideInOffset: {
+ // For vertical stacking, always slide from top
+ if (parent.isTop)
+ return -slideDistance
+ if (parent.isBottom)
+ return slideDistance
+ return 0
+ }
+ readonly property real slideOutOffset: {
+ // Slide out in the same direction as slide in
+ if (parent.isTop)
+ return -slideDistance
+ if (parent.isBottom)
+ return slideDistance
+ return 0
+ }
+
// Animate in when the item is created
Component.onCompleted: {
- scaleValue = 1.0
- opacityValue = 1.0
+ // Start from slide position
+ slideOffset = slideInOffset
+ scaleValue = 0.8
+ opacityValue = 0.0
+
+ // Delay animation based on index for staggered effect
+ delayTimer.interval = animationDelay
+ delayTimer.start()
+ }
+
+ // Timer for staggered animation start
+ Timer {
+ id: delayTimer
+ interval: 0
+ repeat: false
+ onTriggered: {
+ // Animate to final position
+ slideOffset = 0
+ scaleValue = 1.0
+ opacityValue = 1.0
+ }
}
// Animate out when being removed
@@ -272,6 +316,7 @@ Variants {
return
// Prevent multiple animations
isRemoving = true
+ slideOffset = slideOutOffset
scaleValue = 0.8
opacityValue = 0.0
}
@@ -293,46 +338,57 @@ Variants {
}
}
- // Animation behaviors
+ // Animation behaviors with spring physics
Behavior on scale {
- NumberAnimation {
- duration: Style.animationNormal
- easing.type: Easing.OutExpo
+ SpringAnimation {
+ spring: 3
+ damping: 0.4
+ epsilon: 0.01
+ mass: 0.8
}
}
Behavior on opacity {
NumberAnimation {
duration: Style.animationNormal
- easing.type: Easing.OutQuad
+ easing.type: Easing.OutCubic
+ }
+ }
+
+ Behavior on y {
+ SpringAnimation {
+ spring: 2.5
+ damping: 0.3
+ epsilon: 0.01
+ mass: 0.6
}
}
ColumnLayout {
id: notificationLayout
anchors.fill: parent
- anchors.margins: Style.marginM * cardScaling
- anchors.rightMargin: (Style.marginM + 32) * cardScaling // Leave space for close button
- spacing: Style.marginM * cardScaling
+ anchors.margins: Style.marginM
+ anchors.rightMargin: (Style.marginM + 32) // Leave space for close button
+ spacing: Style.marginM
// Main content section
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginM * cardScaling
+ spacing: Style.marginM
ColumnLayout {
// For real-time notification always show the original image
// as the cached version is most likely still processing.
NImageCircled {
- Layout.preferredWidth: 40 * cardScaling
- Layout.preferredHeight: 40 * cardScaling
+ Layout.preferredWidth: Math.round(40 * Style.uiScaleRatio)
+ Layout.preferredHeight: Math.round(40 * Style.uiScaleRatio)
Layout.alignment: Qt.AlignTop
- Layout.topMargin: 30 * cardScaling
+ Layout.topMargin: 30
imagePath: model.originalImage || ""
borderColor: Color.transparent
borderWidth: 0
fallbackIcon: "bell"
- fallbackIconSize: 24 * cardScaling
+ fallbackIconSize: 24
}
Item {
Layout.fillHeight: true
@@ -342,17 +398,17 @@ Variants {
// Text content
ColumnLayout {
Layout.fillWidth: true
- spacing: Style.marginS * cardScaling
+ spacing: Style.marginS
// Header section with app name and timestamp
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginS * cardScaling
+ spacing: Style.marginS
Rectangle {
- Layout.preferredWidth: 6 * cardScaling
- Layout.preferredHeight: 6 * cardScaling
- radius: Style.radiusXS * cardScaling
+ Layout.preferredWidth: 6
+ Layout.preferredHeight: 6
+ radius: Style.radiusXS
color: {
if (model.urgency === NotificationUrgency.Critical || model.urgency === 2)
return Color.mError
@@ -367,7 +423,7 @@ Variants {
NText {
text: `${model.appName || I18n.tr("system.unknown-app")} · ${Time.formatRelativeTime(model.timestamp)}`
color: Color.mSecondary
- pointSize: Style.fontSizeXS * cardScaling
+ pointSize: Style.fontSizeXS
}
Item {
@@ -377,7 +433,7 @@ Variants {
NText {
text: model.summary || I18n.tr("general.no-summary")
- pointSize: Style.fontSizeL * cardScaling
+ pointSize: Style.fontSizeL
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
textFormat: Text.PlainText
@@ -390,7 +446,7 @@ Variants {
NText {
text: model.body || ""
- pointSize: Style.fontSizeM * cardScaling
+ pointSize: Style.fontSizeM
color: Color.mOnSurface
textFormat: Text.PlainText
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
@@ -403,8 +459,8 @@ Variants {
// Notification actions
Flow {
Layout.fillWidth: true
- spacing: Style.marginS * cardScaling
- Layout.topMargin: Style.marginM * cardScaling
+ spacing: Style.marginS
+ Layout.topMargin: Style.marginM
flow: Flow.LeftToRight
layoutDirection: Qt.LeftToRight
@@ -436,12 +492,12 @@ Variants {
}
return actionText
}
- fontSize: Style.fontSizeS * cardScaling
+ fontSize: Style.fontSizeS
backgroundColor: Color.mPrimary
textColor: hovered ? Color.mOnTertiary : Color.mOnPrimary
hoverColor: Color.mTertiary
outlined: false
- implicitHeight: 24 * cardScaling
+ implicitHeight: 24
onClicked: {
NotificationService.invokeAction(parent.parentNotificationId, actionData.identifier)
}
@@ -458,9 +514,9 @@ Variants {
tooltipText: I18n.tr("tooltips.close")
baseSize: Style.baseWidgetSize * 0.6
anchors.top: parent.top
- anchors.topMargin: Style.marginM * cardScaling
+ anchors.topMargin: Style.marginM
anchors.right: parent.right
- anchors.rightMargin: Style.marginM * cardScaling
+ anchors.rightMargin: Style.marginM
onClicked: {
animateOut()
diff --git a/Modules/Notification/NotificationHistoryPanel.qml b/Modules/Notification/NotificationHistoryPanel.qml
index bd830f21..8bf5583f 100644
--- a/Modules/Notification/NotificationHistoryPanel.qml
+++ b/Modules/Notification/NotificationHistoryPanel.qml
@@ -26,23 +26,23 @@ NPanel {
ColumnLayout {
anchors.fill: parent
- anchors.margins: Style.marginL * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginL
+ spacing: Style.marginM
// Header section
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NIcon {
icon: "bell"
- pointSize: Style.fontSizeXXL * scaling
+ pointSize: Style.fontSizeXXL
color: Color.mPrimary
}
NText {
text: I18n.tr("notifications.panel.title")
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.fillWidth: true
@@ -84,7 +84,7 @@ NPanel {
Layout.fillHeight: true
Layout.alignment: Qt.AlignHCenter
visible: NotificationService.historyList.count === 0
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Item {
Layout.fillHeight: true
@@ -92,21 +92,21 @@ NPanel {
NIcon {
icon: "bell-off"
- pointSize: 64 * scaling
+ pointSize: 64
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: I18n.tr("notifications.panel.no-notifications")
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: I18n.tr("notifications.panel.description")
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
@@ -128,7 +128,7 @@ NPanel {
verticalPolicy: ScrollBar.AsNeeded
model: NotificationService.historyList
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
clip: true
boundsBehavior: Flickable.StopAtBounds
visible: NotificationService.historyList.count > 0
@@ -141,11 +141,11 @@ NPanel {
property bool isExpanded: notificationList.expandedId === notificationId
width: notificationList.width
- height: notificationLayout.implicitHeight + (Style.marginM * scaling * 2)
- radius: Style.radiusM * scaling
+ height: notificationLayout.implicitHeight + (Style.marginM * 2)
+ radius: Style.radiusM
color: Color.mSurfaceVariant
border.color: Qt.alpha(Color.mOutline, Style.opacityMedium)
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
Behavior on height {
NumberAnimation {
@@ -165,7 +165,7 @@ NPanel {
MouseArea {
anchors.fill: parent
// Don't capture clicks on the delete button
- anchors.rightMargin: 48 * scaling
+ anchors.rightMargin: 48
enabled: (summaryText.truncated || bodyText.truncated)
onClicked: {
if (notificationList.expandedId === notificationId) {
@@ -180,20 +180,20 @@ NPanel {
RowLayout {
id: notificationLayout
anchors.fill: parent
- anchors.margins: Style.marginM * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginM
+ spacing: Style.marginM
ColumnLayout {
NImageCircled {
- Layout.preferredWidth: 40 * scaling
- Layout.preferredHeight: 40 * scaling
+ Layout.preferredWidth: Math.round(40 * Style.uiScaleRatio)
+ Layout.preferredHeight: Math.round(40 * Style.uiScaleRatio)
Layout.alignment: Qt.AlignTop
- Layout.topMargin: 20 * scaling
+ Layout.topMargin: 20
imagePath: model.cachedImage || model.originalImage || ""
borderColor: Color.transparent
borderWidth: 0
fallbackIcon: "bell"
- fallbackIconSize: 24 * scaling
+ fallbackIconSize: 24
}
Item {
Layout.fillHeight: true
@@ -204,20 +204,20 @@ NPanel {
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
- spacing: Style.marginXS * scaling
- Layout.rightMargin: -(Style.marginM + Style.baseWidgetSize * 0.6) * scaling
+ spacing: Style.marginXS
+ Layout.rightMargin: -(Style.marginM + Style.baseWidgetSize * 0.6)
// Header row with app name and timestamp
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
// Urgency indicator
Rectangle {
- Layout.preferredWidth: 6 * scaling
- Layout.preferredHeight: 6 * scaling
+ Layout.preferredWidth: 6
+ Layout.preferredHeight: 6
Layout.alignment: Qt.AlignVCenter
- radius: 3 * scaling
+ radius: 3
visible: model.urgency !== 1
color: {
if (model.urgency === 2)
@@ -231,13 +231,13 @@ NPanel {
NText {
text: model.appName || "Unknown App"
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
color: Color.mSecondary
}
NText {
text: Time.formatRelativeTime(model.timestamp)
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
color: Color.mSecondary
}
@@ -250,7 +250,7 @@ NPanel {
NText {
id: summaryText
text: model.summary || I18n.tr("general.no-summary")
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
font.weight: Font.Medium
color: Color.mOnSurface
textFormat: Text.PlainText
@@ -264,7 +264,7 @@ NPanel {
NText {
id: bodyText
text: model.body || ""
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
textFormat: Text.PlainText
wrapMode: Text.Wrap
@@ -277,14 +277,14 @@ NPanel {
// Spacer for expand indicator
Item {
Layout.fillWidth: true
- Layout.preferredHeight: (!isExpanded && (summaryText.truncated || bodyText.truncated)) ? (Style.marginS * scaling) : 0
+ Layout.preferredHeight: (!isExpanded && (summaryText.truncated || bodyText.truncated)) ? (Style.marginS) : 0
}
// Expand indicator
RowLayout {
Layout.fillWidth: true
visible: !isExpanded && (summaryText.truncated || bodyText.truncated)
- spacing: Style.marginXS * scaling
+ spacing: Style.marginXS
Item {
Layout.fillWidth: true
@@ -292,14 +292,14 @@ NPanel {
NText {
text: I18n.tr("notifications.panel.click-to-expand") || "Click to expand"
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
color: Color.mPrimary
font.weight: Font.Medium
}
NIcon {
icon: "chevron-down"
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
color: Color.mPrimary
}
}
diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml
index 91c551a8..23c63a0b 100644
--- a/Modules/OSD/OSD.qml
+++ b/Modules/OSD/OSD.qml
@@ -16,7 +16,6 @@ Variants {
id: root
required property ShellScreen modelData
- property real scaling: ScalingService.getScreenScale(modelData)
// Access the notification model from the service
property ListModel notificationModel: NotificationService.activeList
@@ -25,7 +24,7 @@ Variants {
active: false
// Current OSD display state
- property string currentOSDType: "" // "volume", "brightness", or ""
+ property string currentOSDType: "" // "volume", "inputVolume", "brightness", or ""
// Volume properties
readonly property real currentVolume: AudioService.volume
@@ -33,14 +32,16 @@ Variants {
property bool volumeInitialized: false
property bool muteInitialized: false
+ // Input volume properties
+ readonly property real currentInputVolume: AudioService.inputVolume
+ readonly property bool isInputMuted: AudioService.inputMuted
+ property bool inputVolumeInitialized: false
+ property bool inputMuteInitialized: false
+
// Brightness properties
+ property real lastUpdatedBrightness: 0
+ readonly property real currentBrightness: lastUpdatedBrightness
property bool brightnessInitialized: false
- readonly property real currentBrightness: {
- if (BrightnessService.monitors.length > 0) {
- return BrightnessService.monitors[0].brightness || 0
- }
- return 0
- }
// Get appropriate icon based on current OSD type
function getIcon() {
@@ -49,6 +50,11 @@ Variants {
return "volume-mute"
}
return (AudioService.volume <= Number.EPSILON) ? "volume-zero" : (AudioService.volume <= 0.5) ? "volume-low" : "volume-high"
+ } else if (currentOSDType === "inputVolume") {
+ if (AudioService.inputMuted) {
+ return "microphone-off"
+ }
+ return "microphone"
} else if (currentOSDType === "brightness") {
return currentBrightness <= 0.5 ? "brightness-low" : "brightness-high"
}
@@ -59,6 +65,8 @@ Variants {
function getCurrentValue() {
if (currentOSDType === "volume") {
return isMuted ? 0 : currentVolume
+ } else if (currentOSDType === "inputVolume") {
+ return isInputMuted ? 0 : currentInputVolume
} else if (currentOSDType === "brightness") {
return currentBrightness
}
@@ -72,6 +80,11 @@ Variants {
return "0%"
const pct = Math.round(Math.min(1.0, currentVolume) * 100)
return pct + "%"
+ } else if (currentOSDType === "inputVolume") {
+ if (isInputMuted)
+ return "0%"
+ const pct = Math.round(Math.min(1.0, currentInputVolume) * 100)
+ return pct + "%"
} else if (currentOSDType === "brightness") {
const pct = Math.round(Math.min(1.0, currentBrightness) * 100)
return pct + "%"
@@ -85,13 +98,17 @@ Variants {
if (isMuted)
return Color.mError
return Color.mPrimary
+ } else if (currentOSDType === "inputVolume") {
+ if (isInputMuted)
+ return Color.mError
+ return Color.mPrimary
}
return Color.mPrimary
}
// Get icon color
function getIconColor() {
- if (currentOSDType === "volume" && isMuted) {
+ if ((currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted)) {
return Color.mError
}
return Color.mOnSurface
@@ -108,20 +125,13 @@ Variants {
readonly property bool isRight: (location.indexOf("_right") >= 0) || (location === "right")
readonly property bool isCentered: (location === "top" || location === "bottom")
readonly property bool verticalMode: (location === "left" || location === "right")
- readonly property int hWidth: Math.round(320 * root.scaling)
- readonly property int hHeight: Math.round(64 * root.scaling)
+ readonly property int hWidth: Math.round(320 * Style.uiScaleRatio)
+ readonly property int hHeight: Math.round(64 * Style.uiScaleRatio)
+ readonly property int vHeight: hWidth // Vertical OSD height (matches horizontal width)
// Ensure an even width to keep the vertical bar perfectly centered
- readonly property int barThickness: (function () {
- const base = Math.max(6, Math.round(6 * root.scaling))
+ readonly property int barThickness: {
+ const base = Math.max(8, Math.round(8 * Style.uiScaleRatio))
return (base % 2 === 0) ? base : base + 1
- })()
-
- Component.onCompleted: {
- connectBrightnessMonitors()
- }
-
- Component.onDestruction: {
- disconnectBrightnessMonitors()
}
// Anchor selection based on location (window edges)
@@ -134,10 +144,10 @@ Variants {
margins.top: {
if (!(anchors.top))
return 0
- var base = Style.marginM * scaling
+ var base = Style.marginM
if (Settings.data.bar.position === "top") {
- var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
- return (Style.barHeight * scaling) + base + floatExtraV
+ var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0
+ return Style.barHeight + base + floatExtraV
}
return base
}
@@ -145,10 +155,10 @@ Variants {
margins.bottom: {
if (!(anchors.bottom))
return 0
- var base = Style.marginM * scaling
+ var base = Style.marginM
if (Settings.data.bar.position === "bottom") {
- var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
- return (Style.barHeight * scaling) + base + floatExtraV
+ var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0
+ return Style.barHeight + base + floatExtraV
}
return base
}
@@ -156,10 +166,10 @@ Variants {
margins.left: {
if (!(anchors.left))
return 0
- var base = Style.marginM * scaling
+ var base = Style.marginM
if (Settings.data.bar.position === "left") {
- var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
- return (Style.barHeight * scaling) + base + floatExtraH
+ var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
+ return Style.barHeight + base + floatExtraH
}
return base
}
@@ -167,10 +177,10 @@ Variants {
margins.right: {
if (!(anchors.right))
return 0
- var base = Style.marginM * scaling
+ var base = Style.marginM
if (Settings.data.bar.position === "right") {
- var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
- return (Style.barHeight * scaling) + base + floatExtraH
+ var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
+ return Style.barHeight + base + floatExtraH
}
return base
}
@@ -181,26 +191,29 @@ Variants {
color: Color.transparent
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
+ WlrLayershell.layer: (Settings.data.osd && Settings.data.osd.alwaysOnTop) ? WlrLayer.Overlay : WlrLayer.Top
exclusionMode: PanelWindow.ExclusionMode.Ignore
Rectangle {
id: osdItem
width: parent.width
- height: panel.verticalMode ? panel.hWidth : Math.round(64 * root.scaling)
- radius: Style.radiusL * root.scaling
+ height: panel.verticalMode ? panel.vHeight : Math.round(64 * Style.uiScaleRatio)
+ radius: Style.radiusL
color: Color.mSurface
border.color: Color.mOutline
- border.width: (function () {
- const bw = Math.max(2, Math.round(Style.borderM * root.scaling))
+ border.width: {
+ const bw = Math.max(2, Style.borderM)
return (bw % 2 === 0) ? bw : bw + 1
- })()
+ }
visible: false
opacity: 0
- scale: 0.85
+ scale: 0.85 // initial scale for a little zoom effect
- anchors.horizontalCenter: verticalMode ? undefined : parent.horizontalCenter
- anchors.verticalCenter: verticalMode ? parent.verticalCenter : undefined
+ // Only horizontally center when the window itself is centered (top/bottom positions)
+ // For left/right vertical mode, fill the parent width
+ anchors.horizontalCenter: (!panel.verticalMode && panel.isCentered) ? parent.horizontalCenter : undefined
+ anchors.verticalCenter: panel.verticalMode ? parent.verticalCenter : undefined
Behavior on opacity {
NumberAnimation {
@@ -252,13 +265,13 @@ Variants {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
- anchors.margins: Style.marginM * root.scaling
- spacing: Style.marginM * root.scaling
+ anchors.margins: Style.marginL
+ spacing: Style.marginM
NIcon {
icon: root.getIcon()
color: root.getIconColor()
- pointSize: Style.fontSizeXL * root.scaling
+ pointSize: Style.fontSizeXL
Layout.alignment: Qt.AlignVCenter
Behavior on color {
@@ -271,7 +284,7 @@ Variants {
// Progress bar with calculated width
Rectangle {
- Layout.preferredWidth: Math.round(220 * root.scaling)
+ Layout.fillWidth: true
height: panel.barThickness
radius: Math.round(panel.barThickness / 2)
color: Color.mSurfaceVariant
@@ -304,12 +317,12 @@ Variants {
NText {
text: root.getDisplayPercentage()
color: Color.mOnSurface
- pointSize: Style.fontSizeS * root.scaling
+ pointSize: Style.fontSizeS
family: Settings.data.ui.fontFixed
- Layout.alignment: Qt.AlignVCenter
- horizontalAlignment: Text.AlignLeft
+ Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
+ horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
- Layout.preferredWidth: Math.round(50 * root.scaling)
+ Layout.preferredWidth: Math.round(50 * Style.uiScaleRatio)
}
}
}
@@ -317,90 +330,88 @@ Variants {
Component {
id: verticalContent
- ColumnLayout {
- // Ensure inner padding respects the rounded corners; avoid clipping the icon/text
- property int vMargin: (function () {
- const styleMargin = Math.round(Style.marginL * root.scaling)
- const cornerGuard = Math.round(osdItem.radius)
- return Math.max(styleMargin, cornerGuard)
- })()
- property int vMarginTop: Math.max(Math.round(osdItem.radius), Math.round(Style.marginS * root.scaling))
- property int balanceDelta: Math.round(Style.marginS * root.scaling)
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- anchors.topMargin: vMarginTop
- anchors.bottomMargin: vMargin
- width: (function () {
- const w = parent.width - (vMargin * 2)
- return (w % 2 === 0) ? w : w - 1
- })()
- spacing: Math.round(Style.marginS * root.scaling)
+ Item {
+ anchors.fill: parent
- // Percentage text at top
- Item {
- Layout.fillWidth: true
- Layout.preferredHeight: percentText.implicitHeight
- NText {
- id: percentText
- text: root.getDisplayPercentage()
- color: Color.mOnSurface
- pointSize: Style.fontSizeS * root.scaling
- family: Settings.data.ui.fontFixed
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.verticalCenter: parent.verticalCenter
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
+ ColumnLayout {
+ // Ensure inner padding respects the rounded corners; avoid clipping the icon/text
+ property int vMargin: {
+ const styleMargin = Style.marginL
+ const cornerGuard = Math.round(osdItem.radius)
+ return Math.max(styleMargin, cornerGuard)
}
- }
+ property int vMarginTop: Math.round(Math.max(osdItem.radius, Style.marginS))
+ property int balanceDelta: Style.marginS
+ anchors.fill: parent
+ anchors.topMargin: vMargin
+ anchors.bottomMargin: vMargin
+ spacing: Style.marginS
- // Progress bar
- Item {
- Layout.fillWidth: true
- Layout.fillHeight: true
- Rectangle {
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- width: panel.barThickness
- radius: Math.round(panel.barThickness / 2)
- color: Color.mSurfaceVariant
+ // Percentage text at top
+ Item {
+ Layout.fillWidth: true
+ Layout.preferredHeight: Math.round(20 * Style.uiScaleRatio)
+ NText {
+ id: percentText
+ text: root.getDisplayPercentage()
+ color: Color.mOnSurface
+ pointSize: Style.fontSizeS
+ family: Settings.data.ui.fontFixed
+ width: Math.round(50 * Style.uiScaleRatio)
+ height: parent.height
+ anchors.centerIn: parent
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+ // Progress bar
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true // Fill remaining space between text and icon
Rectangle {
- anchors.left: parent.left
- anchors.right: parent.right
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: parent.top
anchors.bottom: parent.bottom
- height: parent.height * Math.min(1.0, root.getCurrentValue())
- radius: parent.radius
- color: root.getProgressColor()
+ width: panel.barThickness
+ radius: Math.round(panel.barThickness / 2)
+ color: Color.mSurfaceVariant
- Behavior on height {
- NumberAnimation {
- duration: Style.animationNormal
- easing.type: Easing.InOutQuad
+ Rectangle {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ height: parent.height * Math.min(1.0, root.getCurrentValue())
+ radius: parent.radius
+ color: root.getProgressColor()
+
+ Behavior on height {
+ NumberAnimation {
+ duration: Style.animationNormal
+ easing.type: Easing.InOutQuad
+ }
}
- }
- Behavior on color {
- ColorAnimation {
- duration: Style.animationNormal
- easing.type: Easing.InOutQuad
+ Behavior on color {
+ ColorAnimation {
+ duration: Style.animationNormal
+ easing.type: Easing.InOutQuad
+ }
}
}
}
}
- }
- // Icon at bottom
- NIcon {
- icon: root.getIcon()
- color: root.getIconColor()
- pointSize: Style.fontSizeXL * root.scaling
- Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
- Layout.bottomMargin: vMargin + Math.round(Style.marginM * root.scaling) + balanceDelta
- Behavior on color {
- ColorAnimation {
- duration: Style.animationNormal
- easing.type: Easing.InOutQuad
+ // Icon at bottom
+ NIcon {
+ icon: root.getIcon()
+ color: root.getIconColor()
+ pointSize: Style.fontSizeL
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
+ Behavior on color {
+ ColorAnimation {
+ duration: Style.animationNormal
+ easing.type: Easing.InOutQuad
+ }
}
}
}
@@ -467,6 +478,18 @@ Variants {
showOSD("volume")
}
}
+
+ function onInputVolumeChanged() {
+ if (inputVolumeInitialized) {
+ showOSD("inputVolume")
+ }
+ }
+
+ function onInputMutedChanged() {
+ if (inputMuteInitialized) {
+ showOSD("inputVolume")
+ }
+ }
}
// Timer to initialize volume/mute flags after services are ready
@@ -477,6 +500,10 @@ Variants {
onTriggered: {
volumeInitialized = true
muteInitialized = true
+ inputVolumeInitialized = true
+ inputMuteInitialized = true
+ // Don't initialize brightness here - let it initialize on first change like volume
+ connectBrightnessMonitors()
}
}
@@ -506,11 +533,14 @@ Variants {
}
function onBrightnessChanged(newBrightness) {
+ root.lastUpdatedBrightness = newBrightness
+
if (!brightnessInitialized) {
brightnessInitialized = true
- } else {
- showOSD("brightness")
+ return
}
+
+ showOSD("brightness")
}
function showOSD(type) {
diff --git a/Modules/SessionMenu/SessionMenu.qml b/Modules/SessionMenu/SessionMenu.qml
index 039c2c98..034a362c 100644
--- a/Modules/SessionMenu/SessionMenu.qml
+++ b/Modules/SessionMenu/SessionMenu.qml
@@ -13,8 +13,8 @@ import qs.Widgets
NPanel {
id: root
- preferredWidth: 440
- preferredHeight: 480
+ preferredWidth: 380 * Style.uiScaleRatio
+ preferredHeight: 440 * Style.uiScaleRatio
panelAnchorHorizontalCenter: true
panelAnchorVerticalCenter: true
panelKeyboardFocus: true
@@ -259,16 +259,16 @@ NPanel {
ColumnLayout {
anchors.fill: parent
- anchors.topMargin: Style.marginL * scaling
- anchors.leftMargin: Style.marginL * scaling
- anchors.rightMargin: Style.marginL * scaling
- anchors.bottomMargin: Style.marginM * scaling
- spacing: Style.marginS * scaling
+ anchors.topMargin: Style.marginL
+ anchors.leftMargin: Style.marginL
+ anchors.rightMargin: Style.marginL
+ anchors.bottomMargin: Style.marginM
+ spacing: Style.marginS
// Header with title and close button
RowLayout {
Layout.fillWidth: true
- Layout.preferredHeight: Style.baseWidgetSize * 0.8 * scaling
+ Layout.preferredHeight: Style.baseWidgetSize * 0.8
NText {
text: timerActive ? I18n.tr("session-menu.action-in-seconds", {
@@ -276,7 +276,7 @@ NPanel {
"seconds": Math.ceil(timeRemaining / 1000)
}) : I18n.tr("session-menu.title")
font.weight: Style.fontWeightBold
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
color: timerActive ? Color.mPrimary : Color.mOnSurface
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
@@ -310,7 +310,7 @@ NPanel {
// Power options
ColumnLayout {
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
Repeater {
model: powerOptions
@@ -345,8 +345,8 @@ NPanel {
signal clicked
- height: Style.baseWidgetSize * 1.6 * scaling
- radius: Style.radiusS * scaling
+ height: Style.baseWidgetSize * 1.6 * Style.uiScaleRatio
+ radius: Style.radiusS
color: {
if (pending) {
return Qt.alpha(Color.mPrimary, 0.08)
@@ -357,7 +357,7 @@ NPanel {
return Color.transparent
}
- border.width: pending ? Math.max(Style.borderM * scaling) : 0
+ border.width: pending ? Math.max(Style.borderM) : 0
border.color: pending ? Color.mPrimary : Color.mOutline
Behavior on color {
@@ -368,7 +368,7 @@ NPanel {
Item {
anchors.fill: parent
- anchors.margins: Style.marginL * scaling
+ anchors.margins: Style.marginL
// Icon on the left
NIcon {
@@ -385,8 +385,8 @@ NPanel {
return Color.mOnTertiary
return Color.mOnSurface
}
- pointSize: Style.fontSizeXXXL * scaling
- width: Style.baseWidgetSize * 0.6 * scaling
+ pointSize: Style.fontSizeXXXL
+ width: Style.baseWidgetSize * 0.6
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
@@ -402,14 +402,14 @@ NPanel {
anchors.left: iconElement.right
anchors.right: pendingIndicator.visible ? pendingIndicator.left : parent.right
anchors.verticalCenter: parent.verticalCenter
- anchors.leftMargin: Style.marginXL * scaling
- anchors.rightMargin: pendingIndicator.visible ? Style.marginM * scaling : 0
+ anchors.leftMargin: Style.marginXL
+ anchors.rightMargin: pendingIndicator.visible ? Style.marginM : 0
spacing: 0
NText {
text: buttonRoot.title
font.weight: Style.fontWeightMedium
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
color: {
if (buttonRoot.pending)
return Color.mPrimary
@@ -434,7 +434,7 @@ NPanel {
}
return buttonRoot.subtitle
}
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
color: {
if (buttonRoot.pending)
return Color.mPrimary
@@ -455,8 +455,8 @@ NPanel {
id: pendingIndicator
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
- width: 24 * scaling
- height: 24 * scaling
+ width: 24
+ height: 24
radius: width * 0.5
color: Color.mPrimary
visible: buttonRoot.pending
@@ -464,7 +464,7 @@ NPanel {
NText {
anchors.centerIn: parent
text: Math.ceil(timeRemaining / 1000)
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
}
diff --git a/Modules/Settings/Bar/BarWidgetSettingsDialog.qml b/Modules/Settings/Bar/BarWidgetSettingsDialog.qml
index 1c2a1aaf..4b35b7fd 100644
--- a/Modules/Settings/Bar/BarWidgetSettingsDialog.qml
+++ b/Modules/Settings/Bar/BarWidgetSettingsDialog.qml
@@ -19,9 +19,9 @@ Popup {
x: (parent.width - width) * 0.5
y: (parent.height - height) * 0.5
- width: Math.max(content.implicitWidth + padding * 2, 500 * scaling)
+ width: Math.max(content.implicitWidth + padding * 2, 500)
height: content.implicitHeight + padding * 2
- padding: Style.marginXL * scaling
+ padding: Style.marginXL
modal: true
onOpened: {
@@ -42,16 +42,16 @@ Popup {
id: bgRect
color: Color.mSurface
- radius: Style.radiusL * scaling
+ radius: Style.radiusL
border.color: Color.mPrimary
- border.width: Math.max(1, Style.borderM * scaling)
+ border.width: Math.max(1, Style.borderM)
}
contentItem: ColumnLayout {
id: content
width: parent.width
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Title
RowLayout {
@@ -61,7 +61,7 @@ Popup {
text: I18n.tr("system.widget-settings-title", {
"widget": widgetSettings.widgetId
})
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mPrimary
Layout.fillWidth: true
@@ -69,7 +69,7 @@ Popup {
NIconButton {
icon: "close"
- tooltipText: "Close"
+ tooltipText: I18n.tr("tooltips.close")
onClicked: widgetSettings.close()
}
}
@@ -91,8 +91,8 @@ Popup {
// Action buttons
RowLayout {
Layout.fillWidth: true
- Layout.topMargin: Style.marginM * scaling
- spacing: Style.marginM * scaling
+ Layout.topMargin: Style.marginM
+ spacing: Style.marginM
Item {
Layout.fillWidth: true
@@ -134,7 +134,8 @@ Popup {
"SystemMonitor": "WidgetSettings/SystemMonitorSettings.qml",
"Volume": "WidgetSettings/VolumeSettings.qml",
"Workspace": "WidgetSettings/WorkspaceSettings.qml",
- "Taskbar": "WidgetSettings/TaskbarSettings.qml"
+ "Taskbar": "WidgetSettings/TaskbarSettings.qml",
+ "Tray": "WidgetSettings/TraySettings.qml"
}
const source = widgetSettingsMap[widgetId]
diff --git a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml
index 3d1be163..643684dd 100644
--- a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml
@@ -7,7 +7,7 @@ import qs.Services
ColumnLayout {
id: root
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Properties to receive data from parent
property var widgetData: null
@@ -15,25 +15,43 @@ ColumnLayout {
// Local state
property bool valueShowIcon: widgetData.showIcon !== undefined ? widgetData.showIcon : widgetMetadata.showIcon
- property bool valueAutoHide: widgetData.autoHide !== undefined ? widgetData.autoHide : widgetMetadata.autoHide
+ property string valueHideMode: "hidden" // Default to 'Hide When Empty'
property string valueScrollingMode: widgetData.scrollingMode || widgetMetadata.scrollingMode
property int valueWidth: widgetData.width !== undefined ? widgetData.width : widgetMetadata.width
+ property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons
+
+ Component.onCompleted: {
+ if (widgetData && widgetData.hideMode !== undefined) {
+ valueHideMode = widgetData.hideMode
+ }
+ }
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
- settings.autoHide = valueAutoHide
+ settings.hideMode = valueHideMode
settings.showIcon = valueShowIcon
settings.scrollingMode = valueScrollingMode
settings.width = parseInt(widthInput.text) || widgetMetadata.width
+ settings.colorizeIcons = valueColorizeIcons
return settings
}
- NToggle {
+ NComboBox {
Layout.fillWidth: true
- label: I18n.tr("bar.widget-settings.active-window.auto-hide.label")
- description: I18n.tr("bar.widget-settings.active-window.auto-hide.description")
- checked: root.valueAutoHide
- onToggled: checked => root.valueAutoHide = checked
+ label: I18n.tr("bar.widget-settings.active-window.hide-mode.label")
+ description: I18n.tr("bar.widget-settings.active-window.hide-mode.description")
+ model: [{
+ "key": "visible",
+ "name": I18n.tr("options.hide-modes.visible")
+ }, {
+ "key": "hidden",
+ "name": I18n.tr("options.hide-modes.hidden")
+ }, {
+ "key": "transparent",
+ "name": I18n.tr("options.hide-modes.transparent")
+ }]
+ currentKey: root.valueHideMode
+ onSelected: key => root.valueHideMode = key
}
NToggle {
@@ -44,6 +62,14 @@ ColumnLayout {
onToggled: checked => root.valueShowIcon = checked
}
+ NToggle {
+ Layout.fillWidth: true
+ label: I18n.tr("bar.widget-settings.active-window.colorize-icons.label")
+ description: I18n.tr("bar.widget-settings.active-window.colorize-icons.description")
+ checked: root.valueColorizeIcons
+ onToggled: checked => root.valueColorizeIcons = checked
+ }
+
NTextInput {
id: widthInput
Layout.fillWidth: true
@@ -68,6 +94,6 @@ ColumnLayout {
}]
currentKey: valueScrollingMode
onSelected: key => valueScrollingMode = key
- minimumWidth: 200 * scaling
+ minimumWidth: 200
}
}
diff --git a/Modules/Settings/Bar/WidgetSettings/BatterySettings.qml b/Modules/Settings/Bar/WidgetSettings/BatterySettings.qml
index addf7ff8..8377958d 100644
--- a/Modules/Settings/Bar/WidgetSettings/BatterySettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/BatterySettings.qml
@@ -7,7 +7,7 @@ import qs.Services
ColumnLayout {
id: root
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Properties to receive data from parent
property var widgetData: null
@@ -27,7 +27,7 @@ ColumnLayout {
NComboBox {
label: I18n.tr("bar.widget-settings.battery.display-mode.label")
description: I18n.tr("bar.widget-settings.battery.display-mode.description")
- minimumWidth: 134 * scaling
+ minimumWidth: 134
model: [{
"key": "onhover",
"name": I18n.tr("options.display-mode.on-hover")
diff --git a/Modules/Settings/Bar/WidgetSettings/BrightnessSettings.qml b/Modules/Settings/Bar/WidgetSettings/BrightnessSettings.qml
index c21d7aae..bc572776 100644
--- a/Modules/Settings/Bar/WidgetSettings/BrightnessSettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/BrightnessSettings.qml
@@ -7,7 +7,7 @@ import qs.Services
ColumnLayout {
id: root
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Properties to receive data from parent
property var widgetData: null
@@ -25,7 +25,7 @@ ColumnLayout {
NComboBox {
label: I18n.tr("bar.widget-settings.brightness.display-mode.label")
description: I18n.tr("bar.widget-settings.brightness.display-mode.description")
- minimumWidth: 134 * scaling
+ minimumWidth: 134
model: [{
"key": "onhover",
"name": I18n.tr("options.display-mode.on-hover")
diff --git a/Modules/Settings/Bar/WidgetSettings/ClockSettings.qml b/Modules/Settings/Bar/WidgetSettings/ClockSettings.qml
index d76c8d48..9729ccce 100644
--- a/Modules/Settings/Bar/WidgetSettings/ClockSettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/ClockSettings.qml
@@ -7,8 +7,8 @@ import qs.Services
ColumnLayout {
id: root
- spacing: Style.marginM * scaling
- width: 700 * scaling
+ spacing: Style.marginM
+ width: 700
// Properties to receive data from parent
property var widgetData: null
@@ -89,8 +89,8 @@ ColumnLayout {
currentKey: valueCustomFont
placeholder: I18n.tr("bar.widget-settings.clock.custom-font.placeholder")
searchPlaceholder: I18n.tr("bar.widget-settings.clock.custom-font.search-placeholder")
- popupHeight: 420 * scaling
- minimumWidth: 300 * scaling
+ popupHeight: 420
+ minimumWidth: 300
onSelected: function (key) {
valueCustomFont = key
}
@@ -108,12 +108,12 @@ ColumnLayout {
RowLayout {
id: main
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
ColumnLayout {
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
Layout.fillWidth: true
Layout.preferredWidth: 1 // Equal sizing hint
@@ -175,13 +175,13 @@ ColumnLayout {
}
Rectangle {
- Layout.preferredWidth: 320 * scaling
- Layout.preferredHeight: 160 * scaling // Fixed height instead of fillHeight
+ Layout.preferredWidth: 320
+ Layout.preferredHeight: 160 // Fixed height instead of fillHeight
color: Color.mSurfaceVariant
- radius: Style.radiusM * scaling
+ radius: Style.radiusM
border.color: Color.mSecondary
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
Behavior on border.color {
ColorAnimation {
@@ -190,22 +190,22 @@ ColumnLayout {
}
ColumnLayout {
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
anchors.centerIn: parent
ColumnLayout {
- spacing: -2 * scaling
+ spacing: -2
Layout.alignment: Qt.AlignHCenter
// Horizontal
Repeater {
- Layout.topMargin: Style.marginM * scaling
+ Layout.topMargin: Style.marginM
model: Qt.locale().toString(now, valueFormatHorizontal.trim()).split("\\n")
delegate: NText {
visible: text !== ""
text: modelData
family: valueUseCustomFont && valueCustomFont ? valueCustomFont : Settings.data.ui.fontDefault
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
font.weight: Style.fontWeightBold
color: valueUsePrimaryColor ? Color.mPrimary : Color.mOnSurface
wrapMode: Text.WordWrap
@@ -226,17 +226,17 @@ ColumnLayout {
// Vertical
ColumnLayout {
- spacing: -2 * scaling
+ spacing: -2
Layout.alignment: Qt.AlignHCenter
Repeater {
- Layout.topMargin: Style.marginM * scaling
+ Layout.topMargin: Style.marginM
model: Qt.locale().toString(now, valueFormatVertical.trim()).split(" ")
delegate: NText {
visible: text !== ""
text: modelData
family: valueUseCustomFont && valueCustomFont ? valueCustomFont : Settings.data.ui.fontDefault
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
font.weight: Style.fontWeightBold
color: valueUsePrimaryColor ? Color.mPrimary : Color.mOnSurface
wrapMode: Text.WordWrap
@@ -256,13 +256,13 @@ ColumnLayout {
}
NDivider {
- Layout.topMargin: Style.marginM * scaling
- Layout.bottomMargin: Style.marginM * scaling
+ Layout.topMargin: Style.marginM
+ Layout.bottomMargin: Style.marginM
}
NDateTimeTokens {
Layout.fillWidth: true
- height: 200 * scaling
+ height: 200
// Connect to token clicked signal if NDateTimeTokens provides it
onTokenClicked: token => root.insertToken(token)
diff --git a/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml b/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml
index ac4ae04c..2c0383e2 100644
--- a/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml
@@ -8,7 +8,7 @@ import qs.Services
ColumnLayout {
id: root
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Properties to receive data from parent
property var widgetData: null
@@ -41,7 +41,7 @@ ColumnLayout {
}
RowLayout {
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NLabel {
label: I18n.tr("bar.widget-settings.control-center.icon.label")
@@ -49,23 +49,23 @@ ColumnLayout {
}
NImageCircled {
+ Layout.preferredWidth: Style.fontSizeXL * 2
+ Layout.preferredHeight: Style.fontSizeXL * 2
Layout.alignment: Qt.AlignVCenter
imagePath: valueCustomIconPath
visible: valueCustomIconPath !== ""
- width: Style.fontSizeXL * 2 * scaling
- height: Style.fontSizeXL * 2 * scaling
}
NIcon {
Layout.alignment: Qt.AlignVCenter
icon: valueIcon
- pointSize: Style.fontSizeXXL * 1.5 * scaling
+ pointSize: Style.fontSizeXXL * 1.5
visible: valueIcon !== "" && valueCustomIconPath === ""
}
}
RowLayout {
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NButton {
enabled: !valueUseDistroLogo
text: I18n.tr("bar.widget-settings.control-center.browse-library")
diff --git a/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml b/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml
index 0520740f..c3a6aefe 100644
--- a/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml
@@ -8,7 +8,7 @@ import qs.Services
ColumnLayout {
id: root
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
property var widgetData: null
property var widgetMetadata: null
@@ -27,7 +27,7 @@ ColumnLayout {
}
RowLayout {
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NLabel {
label: I18n.tr("bar.widget-settings.custom-button.icon.label")
@@ -37,7 +37,7 @@ ColumnLayout {
NIcon {
Layout.alignment: Qt.AlignVCenter
icon: valueIcon
- pointSize: Style.fontSizeXL * scaling
+ pointSize: Style.fontSizeXL
visible: valueIcon !== ""
}
diff --git a/Modules/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml b/Modules/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml
index d3551044..86ada35a 100644
--- a/Modules/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml
@@ -7,7 +7,7 @@ import qs.Services
ColumnLayout {
id: root
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Properties to receive data from parent
property var widgetData: null
@@ -25,7 +25,7 @@ ColumnLayout {
NComboBox {
label: I18n.tr("bar.widget-settings.keyboard-layout.display-mode.label")
description: I18n.tr("bar.widget-settings.keyboard-layout.display-mode.description")
- minimumWidth: 134 * scaling
+ minimumWidth: 134
model: [{
"key": "onhover",
"name": I18n.tr("options.display-mode.on-hover")
diff --git a/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml b/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml
index 184a37e5..1d03c87f 100644
--- a/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml
@@ -7,22 +7,28 @@ import qs.Services
ColumnLayout {
id: root
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
// Local state
- property bool valueAutoHide: widgetData.autoHide !== undefined ? widgetData.autoHide : widgetMetadata.autoHide
+ property string valueHideMode: "hidden" // Default to 'Hide When Empty'
property bool valueShowAlbumArt: widgetData.showAlbumArt !== undefined ? widgetData.showAlbumArt : widgetMetadata.showAlbumArt
property bool valueShowVisualizer: widgetData.showVisualizer !== undefined ? widgetData.showVisualizer : widgetMetadata.showVisualizer
property string valueVisualizerType: widgetData.visualizerType || widgetMetadata.visualizerType
property string valueScrollingMode: widgetData.scrollingMode || widgetMetadata.scrollingMode
+ Component.onCompleted: {
+ if (widgetData && widgetData.hideMode !== undefined) {
+ valueHideMode = widgetData.hideMode
+ }
+ }
+
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
- settings.autoHide = valueAutoHide
+ settings.hideMode = valueHideMode
settings.showAlbumArt = valueShowAlbumArt
settings.showVisualizer = valueShowVisualizer
settings.visualizerType = valueVisualizerType
@@ -30,12 +36,22 @@ ColumnLayout {
return settings
}
- NToggle {
+ NComboBox {
Layout.fillWidth: true
- label: I18n.tr("bar.widget-settings.media-mini.auto-hide.label")
- description: I18n.tr("bar.widget-settings.media-mini.auto-hide.description")
- checked: root.valueAutoHide
- onToggled: checked => root.valueAutoHide = checked
+ label: I18n.tr("bar.widget-settings.media-mini.hide-mode.label")
+ description: I18n.tr("bar.widget-settings.media-mini.hide-mode.description")
+ model: [{
+ "key": "visible",
+ "name": I18n.tr("options.hide-modes.visible")
+ }, {
+ "key": "hidden",
+ "name": I18n.tr("options.hide-modes.hidden")
+ }, {
+ "key": "transparent",
+ "name": I18n.tr("options.hide-modes.transparent")
+ }]
+ currentKey: root.valueHideMode
+ onSelected: key => root.valueHideMode = key
}
NToggle {
@@ -68,7 +84,7 @@ ColumnLayout {
}]
currentKey: valueVisualizerType
onSelected: key => valueVisualizerType = key
- minimumWidth: 200 * scaling
+ minimumWidth: 200
}
NComboBox {
@@ -86,6 +102,6 @@ ColumnLayout {
}]
currentKey: valueScrollingMode
onSelected: key => valueScrollingMode = key
- minimumWidth: 200 * scaling
+ minimumWidth: 200
}
}
diff --git a/Modules/Settings/Bar/WidgetSettings/MicrophoneSettings.qml b/Modules/Settings/Bar/WidgetSettings/MicrophoneSettings.qml
index 5091474d..e62ac1da 100644
--- a/Modules/Settings/Bar/WidgetSettings/MicrophoneSettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/MicrophoneSettings.qml
@@ -7,7 +7,7 @@ import qs.Services
ColumnLayout {
id: root
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Properties to receive data from parent
property var widgetData: null
@@ -25,7 +25,7 @@ ColumnLayout {
NComboBox {
label: I18n.tr("bar.widget-settings.microphone.display-mode.label")
description: I18n.tr("bar.widget-settings.microphone.display-mode.description")
- minimumWidth: 134 * scaling
+ minimumWidth: 134
model: [{
"key": "onhover",
"name": I18n.tr("options.display-mode.on-hover")
diff --git a/Modules/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml b/Modules/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml
index 4510a605..61c4e10d 100644
--- a/Modules/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml
@@ -7,7 +7,7 @@ import qs.Services
ColumnLayout {
id: root
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Properties to receive data from parent
property var widgetData: null
diff --git a/Modules/Settings/Bar/WidgetSettings/SpacerSettings.qml b/Modules/Settings/Bar/WidgetSettings/SpacerSettings.qml
index 7c6b8b17..9679c619 100644
--- a/Modules/Settings/Bar/WidgetSettings/SpacerSettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/SpacerSettings.qml
@@ -7,7 +7,7 @@ import qs.Services
ColumnLayout {
id: root
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Properties to receive data from parent
property var widgetData: null
diff --git a/Modules/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml b/Modules/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml
index 852ea8d8..8133ab18 100644
--- a/Modules/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml
@@ -7,7 +7,7 @@ import qs.Services
ColumnLayout {
id: root
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Properties to receive data from parent
property var widgetData: null
diff --git a/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml b/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml
index 98df4c3f..e98c0eaa 100644
--- a/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml
@@ -7,7 +7,7 @@ import qs.Services
ColumnLayout {
id: root
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Properties to receive data from parent
property var widgetData: null
@@ -16,12 +16,13 @@ ColumnLayout {
// Local state
property bool valueOnlyActiveWorkspaces: widgetData.onlyActiveWorkspaces !== undefined ? widgetData.onlyActiveWorkspaces : widgetMetadata.onlyActiveWorkspaces
property bool valueOnlySameOutput: widgetData.onlySameOutput !== undefined ? widgetData.onlySameOutput : widgetMetadata.onlySameOutput
+ property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.onlySameOutput = valueOnlySameOutput
settings.onlyActiveWorkspaces = valueOnlyActiveWorkspaces
- console.log(JSON.stringify(settings))
+ settings.colorizeIcons = valueColorizeIcons
return settings
}
@@ -40,4 +41,12 @@ ColumnLayout {
checked: root.valueOnlyActiveWorkspaces
onToggled: checked => root.valueOnlyActiveWorkspaces = checked
}
+
+ NToggle {
+ Layout.fillWidth: true
+ label: I18n.tr("bar.widget-settings.taskbar.colorize-icons.label")
+ description: I18n.tr("bar.widget-settings.taskbar.colorize-icons.description")
+ checked: root.valueColorizeIcons
+ onToggled: checked => root.valueColorizeIcons = checked
+ }
}
diff --git a/Modules/Settings/Bar/WidgetSettings/TraySettings.qml b/Modules/Settings/Bar/WidgetSettings/TraySettings.qml
new file mode 100644
index 00000000..a9a41243
--- /dev/null
+++ b/Modules/Settings/Bar/WidgetSettings/TraySettings.qml
@@ -0,0 +1,151 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import qs.Commons
+import qs.Widgets
+
+ColumnLayout {
+ id: root
+ // Properties to receive data from parent
+ property var widgetData: ({}) // Expected by BarWidgetSettingsDialog
+ property var widgetMetadata: ({}) // Expected by BarWidgetSettingsDialog
+
+ // Local state
+ property var localBlacklist: widgetData.blacklist || []
+ property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons
+
+ ListModel {
+ id: blacklistModel
+ }
+
+ Component.onCompleted: {
+ // Populate the ListModel from localBlacklist
+ for (var i = 0; i < localBlacklist.length; i++) {
+ blacklistModel.append({
+ "rule": localBlacklist[i]
+ })
+ }
+ }
+
+ spacing: Style.marginM
+
+ NToggle {
+ Layout.fillWidth: true
+ label: I18n.tr("bar.widget-settings.tray.colorize-icons.label")
+ description: I18n.tr("bar.widget-settings.tray.colorize-icons.description")
+ checked: root.valueColorizeIcons
+ onToggled: checked => root.valueColorizeIcons = checked
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: Style.marginS
+
+ NLabel {
+ label: I18n.tr("settings.bar.tray.blacklist.label")
+ description: I18n.tr("settings.bar.tray.blacklist.description")
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: Style.marginS
+
+ NTextInput {
+ id: newRuleInput
+ Layout.fillWidth: true
+ placeholderText: I18n.tr("settings.bar.tray.blacklist.placeholder")
+ }
+
+ NIconButton {
+ Layout.alignment: Qt.AlignVCenter
+ icon: "add"
+ baseSize: Style.baseWidgetSize * 0.8
+ onClicked: {
+ if (newRuleInput.text.length > 0) {
+ var newRule = newRuleInput.text.trim()
+ var exists = false
+ for (var i = 0; i < blacklistModel.count; i++) {
+ if (blacklistModel.get(i).rule === newRule) {
+ exists = true
+ break
+ }
+ }
+ if (!exists) {
+ blacklistModel.append({
+ "rule": newRule
+ })
+ newRuleInput.text = ""
+ }
+ }
+ }
+ enabled: newRuleInput.text.length > 0
+ }
+ }
+ }
+
+ // List of current blacklist items
+ ListView {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 150
+ Layout.topMargin: Style.marginL // Increased top margin
+ clip: true
+ model: blacklistModel
+ delegate: Item {
+ width: ListView.width
+ height: 40
+
+ Rectangle {
+ id: itemBackground
+ anchors.fill: parent
+ anchors.margins: Style.marginXS
+ color: Color.transparent // Make background transparent
+ border.color: Color.mOutline
+ border.width: Math.max(1, Style.borderS)
+ radius: Style.radiusS
+ visible: model.rule !== undefined && model.rule !== "" // Only visible if rule exists
+ }
+
+ Row {
+ anchors.fill: parent
+ anchors.leftMargin: Style.marginS
+ anchors.rightMargin: Style.marginS
+ spacing: Style.marginS
+
+ NText {
+ text: model.rule
+ elide: Text.ElideRight
+ verticalAlignment: Text.AlignVCenter
+ Layout.fillWidth: true
+ }
+
+ NIconButton {
+ width: 16
+ height: 16
+ icon: "close"
+ baseSize: 8
+ colorBg: Color.mSurfaceVariant
+ colorFg: Color.mOnSurface
+ colorBgHover: Color.mError
+ colorFgHover: Color.mOnError
+ onClicked: {
+ blacklistModel.remove(index)
+ }
+ }
+ }
+ }
+ }
+
+ // This function will be called by the dialog to get the new settings
+ function saveSettings() {
+ var newBlacklist = []
+ for (var i = 0; i < blacklistModel.count; i++) {
+ newBlacklist.push(blacklistModel.get(i).rule)
+ }
+
+ // Return the updated settings for this widget instance
+ var settings = Object.assign({}, widgetData || {})
+ settings.blacklist = newBlacklist
+ settings.colorizeIcons = root.valueColorizeIcons
+ return settings
+ }
+}
diff --git a/Modules/Settings/Bar/WidgetSettings/VolumeSettings.qml b/Modules/Settings/Bar/WidgetSettings/VolumeSettings.qml
index 2781f4a6..3b131d38 100644
--- a/Modules/Settings/Bar/WidgetSettings/VolumeSettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/VolumeSettings.qml
@@ -7,7 +7,7 @@ import qs.Services
ColumnLayout {
id: root
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Properties to receive data from parent
property var widgetData: null
@@ -25,7 +25,7 @@ ColumnLayout {
NComboBox {
label: I18n.tr("bar.widget-settings.volume.display-mode.label")
description: I18n.tr("bar.widget-settings.volume.display-mode.description")
- minimumWidth: 134 * scaling
+ minimumWidth: 134
model: [{
"key": "onhover",
"name": I18n.tr("options.display-mode.on-hover")
diff --git a/Modules/Settings/Bar/WidgetSettings/WorkspaceSettings.qml b/Modules/Settings/Bar/WidgetSettings/WorkspaceSettings.qml
index b44f16b9..fb63a593 100644
--- a/Modules/Settings/Bar/WidgetSettings/WorkspaceSettings.qml
+++ b/Modules/Settings/Bar/WidgetSettings/WorkspaceSettings.qml
@@ -7,7 +7,7 @@ import qs.Services
ColumnLayout {
id: root
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Properties to receive data from parent
property var widgetData: null
@@ -37,7 +37,7 @@ ColumnLayout {
}]
currentKey: widgetData.labelMode || widgetMetadata.labelMode
onSelected: key => labelModeCombo.currentKey = key
- minimumWidth: 200 * scaling
+ minimumWidth: 200
}
NToggle {
diff --git a/Modules/Settings/SettingsPanel.qml b/Modules/Settings/SettingsPanel.qml
index d13f5e9e..e5b64566 100644
--- a/Modules/Settings/SettingsPanel.qml
+++ b/Modules/Settings/SettingsPanel.qml
@@ -11,14 +11,11 @@ import qs.Widgets
NPanel {
id: root
- preferredWidth: 1000
- preferredHeight: 1000
- preferredWidthRatio: 0.4
- preferredHeightRatio: 0.75
+ preferredWidth: 820 * Style.uiScaleRatio
+ preferredHeight: 940 * Style.uiScaleRatio
panelAnchorHorizontalCenter: true
panelAnchorVerticalCenter: true
-
panelKeyboardFocus: true
draggable: !PanelService.hasOpenedPopup
@@ -29,6 +26,7 @@ NPanel {
Audio,
Bar,
ColorScheme,
+ ControlCenter,
OSD,
Display,
Dock,
@@ -39,6 +37,7 @@ NPanel {
Network,
Notifications,
ScreenRecorder,
+ UserInterface,
Wallpaper
}
@@ -111,7 +110,14 @@ NPanel {
id: notificationsTab
NotificationsTab {}
}
-
+ Component {
+ id: controlCenterTab
+ ControlCenterTab {}
+ }
+ Component {
+ id: userInterfaceTab
+ UserInterfaceTab {}
+ }
// Order *DOES* matter
function updateTabsModel() {
let newTabs = [{
@@ -119,11 +125,21 @@ NPanel {
"label": "settings.general.title",
"icon": "settings-general",
"source": generalTab
+ }, {
+ "id": SettingsPanel.Tab.UserInterface,
+ "label": "settings.user-interface.title",
+ "icon": "settings-user-interface",
+ "source": userInterfaceTab
}, {
"id": SettingsPanel.Tab.Bar,
"label": "settings.bar.title",
"icon": "settings-bar",
"source": barTab
+ }, {
+ "id": SettingsPanel.Tab.ControlCenter,
+ "label": "settings.control-center.title",
+ "icon": "settings-control-center",
+ "source": controlCenterTab
}, {
"id": SettingsPanel.Tab.Dock,
"label": "settings.dock.title",
@@ -262,7 +278,7 @@ NPanel {
// Main layout container that fills the panel
ColumnLayout {
anchors.fill: parent
- anchors.margins: Style.marginL * scaling
+ anchors.margins: Style.marginL
spacing: 0
// Keyboard shortcuts container
@@ -325,18 +341,18 @@ NPanel {
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginL
// Sidebar
Rectangle {
id: sidebar
- Layout.preferredWidth: 220 * scaling
+ Layout.preferredWidth: 220 * Style.uiScaleRatio
Layout.fillHeight: true
Layout.alignment: Qt.AlignTop
color: Color.mSurfaceVariant
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
- radius: Style.radiusM * scaling
+ border.width: Math.max(1, Style.borderS)
+ radius: Style.radiusM
MouseArea {
anchors.fill: parent
@@ -357,8 +373,8 @@ NPanel {
ColumnLayout {
anchors.fill: parent
- anchors.margins: Style.marginS * scaling
- spacing: Style.marginXS * scaling
+ anchors.margins: Style.marginS
+ spacing: Style.marginXS
Repeater {
id: sections
@@ -366,8 +382,8 @@ NPanel {
delegate: Rectangle {
id: tabItem
Layout.fillWidth: true
- Layout.preferredHeight: tabEntryRow.implicitHeight + Style.marginS * scaling * 2
- radius: Style.radiusS * scaling
+ Layout.preferredHeight: tabEntryRow.implicitHeight + Style.marginM * 2
+ radius: Style.radiusS
color: selected ? Color.mPrimary : (tabItem.hovering ? Color.mTertiary : Color.transparent)
readonly property bool selected: index === currentTabIndex
property bool hovering: false
@@ -388,22 +404,22 @@ NPanel {
RowLayout {
id: tabEntryRow
anchors.fill: parent
- anchors.leftMargin: Style.marginS * scaling
- anchors.rightMargin: Style.marginS * scaling
- spacing: Style.marginM * scaling
+ anchors.leftMargin: Style.marginS
+ anchors.rightMargin: Style.marginS
+ spacing: Style.marginM
// Tab icon
NIcon {
icon: modelData.icon
color: tabTextColor
- pointSize: Style.fontSizeXL * scaling
+ pointSize: Style.fontSizeXL
}
// Tab label
NText {
text: I18n.tr(modelData.label)
color: tabTextColor
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
font.weight: Style.fontWeightBold
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
@@ -434,34 +450,34 @@ NPanel {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignTop
- radius: Style.radiusM * scaling
+ radius: Style.radiusM
color: Color.mSurfaceVariant
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
ColumnLayout {
id: contentLayout
anchors.fill: parent
- anchors.margins: Style.marginL * scaling
- spacing: Style.marginS * scaling
+ anchors.margins: Style.marginL
+ spacing: Style.marginS
// Header row
RowLayout {
id: headerRow
Layout.fillWidth: true
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
// Main icon
NIcon {
icon: root.tabsModel[currentTabIndex]?.icon
color: Color.mPrimary
- pointSize: Style.fontSizeXXL * scaling
+ pointSize: Style.fontSizeXXL
}
// Main title
NText {
text: I18n.tr(root.tabsModel[currentTabIndex]?.label) || ""
- pointSize: Style.fontSizeXL * scaling
+ pointSize: Style.fontSizeXL
font.weight: Style.fontWeightBold
color: Color.mPrimary
Layout.fillWidth: true
@@ -516,7 +532,7 @@ NPanel {
anchors.fill: parent
horizontalPolicy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AsNeeded
- padding: Style.marginL * scaling
+ padding: Style.marginL
Component.onCompleted: {
root.activeScrollView = scrollView
}
diff --git a/Modules/Settings/Tabs/AboutTab.qml b/Modules/Settings/Tabs/AboutTab.qml
index 6e226e1b..1faf3a53 100644
--- a/Modules/Settings/Tabs/AboutTab.qml
+++ b/Modules/Settings/Tabs/AboutTab.qml
@@ -15,7 +15,7 @@ ColumnLayout {
property string currentVersion: UpdateService.currentVersion
property var contributors: GitHubService.contributors
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
NHeader {
label: I18n.tr("settings.about.noctalia.section.label")
@@ -23,13 +23,13 @@ ColumnLayout {
}
RowLayout {
- spacing: Style.marginXL * scaling
+ spacing: Style.marginXL
// Versions
GridLayout {
columns: 2
- rowSpacing: Style.marginXS * scaling
- columnSpacing: Style.marginS * scaling
+ rowSpacing: Style.marginXS
+ columnSpacing: Style.marginS
NText {
text: I18n.tr("settings.about.noctalia.latest-version")
@@ -76,17 +76,66 @@ ColumnLayout {
icon: "download"
text: I18n.tr("settings.about.noctalia.download-latest")
outlined: !hovered
- fontSize: Style.fontSizeXS * scaling
+ fontSize: Style.fontSizeXS
onClicked: {
Quickshell.execDetached(["xdg-open", "https://github.com/Ly-sec/Noctalia/releases/latest"])
}
}
}
+ // Ko-fi support button
+ Rectangle {
+ Layout.alignment: Qt.AlignHCenter
+ Layout.topMargin: Style.marginM
+ Layout.bottomMargin: Style.marginM
+ width: supportRow.implicitWidth + Style.marginXL
+ height: supportRow.implicitHeight + Style.marginM
+ radius: Style.radiusS
+ color: supportArea.containsMouse ? Qt.alpha(Color.mOnSurface, 0.05) : Color.transparent
+ border.width: 0
+
+ Behavior on color {
+ ColorAnimation {
+ duration: Style.animationFast
+ }
+ }
+
+ RowLayout {
+ id: supportRow
+ anchors.centerIn: parent
+ spacing: Style.marginS
+
+ NText {
+ text: I18n.tr("settings.about.support")
+ pointSize: Style.fontSizeXS
+ color: Color.mOnSurface
+ opacity: supportArea.containsMouse ? Style.opacityFull : Style.opacityMedium
+ }
+
+ NIcon {
+ icon: supportArea.containsMouse ? "heart-filled" : "heart"
+ pointSize: 14
+ color: Color.mOnSurface
+ opacity: supportArea.containsMouse ? Style.opacityFull : Style.opacityMedium
+ }
+ }
+
+ MouseArea {
+ id: supportArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ Quickshell.execDetached(["xdg-open", "https://ko-fi.com/lysec"])
+ ToastService.showNotice(I18n.tr("settings.about.support"), I18n.tr("toast.kofi.opened"), 3000)
+ }
+ }
+ }
+
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXXXL
+ Layout.bottomMargin: Style.marginXL
}
// Contributors
@@ -101,23 +150,26 @@ ColumnLayout {
GridView {
id: contributorsGrid
+
+ readonly property int columnsCount: 2
+
Layout.alignment: Qt.AlignHCenter
- Layout.preferredWidth: cellWidth * 3 // Fixed 3 columns
+ Layout.preferredWidth: cellWidth * columnsCount
Layout.preferredHeight: {
if (root.contributors.length === 0)
return 0
- const columns = 3
- const rows = Math.ceil(root.contributors.length / columns)
+
+ const rows = Math.ceil(root.contributors.length / columnsCount)
return rows * cellHeight
}
- cellWidth: Style.baseWidgetSize * 7 * scaling
- cellHeight: Style.baseWidgetSize * 3 * scaling
+ cellWidth: Math.round(Style.baseWidgetSize * 7)
+ cellHeight: Math.round(Style.baseWidgetSize * 2.5)
model: root.contributors
delegate: Rectangle {
- width: contributorsGrid.cellWidth - Style.marginM * scaling
- height: contributorsGrid.cellHeight - Style.marginM * scaling
- radius: Style.radiusL * scaling
+ width: contributorsGrid.cellWidth - Style.marginM
+ height: contributorsGrid.cellHeight - Style.marginM
+ radius: Style.radiusL
color: contributorArea.containsMouse ? Color.mTertiary : Color.transparent
Behavior on color {
@@ -128,21 +180,21 @@ ColumnLayout {
RowLayout {
anchors.fill: parent
- anchors.margins: Style.marginS * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginS
+ spacing: Style.marginM
Item {
Layout.alignment: Qt.AlignVCenter
- Layout.preferredWidth: Style.baseWidgetSize * 2 * scaling
- Layout.preferredHeight: Style.baseWidgetSize * 2 * scaling
+ Layout.preferredWidth: Style.baseWidgetSize * 2 * Style.uiScaleRatio
+ Layout.preferredHeight: Style.baseWidgetSize * 2 * Style.uiScaleRatio
NImageCircled {
imagePath: modelData.avatar_url || ""
anchors.fill: parent
- anchors.margins: Style.marginXS * scaling
+ anchors.margins: Style.marginXS
fallbackIcon: "person"
borderColor: contributorArea.containsMouse ? Color.mOnTertiary : Color.mPrimary
- borderWidth: Math.max(1, Style.borderM * scaling)
+ borderWidth: Math.max(1, Style.borderM)
Behavior on borderColor {
ColorAnimation {
@@ -153,7 +205,7 @@ ColumnLayout {
}
ColumnLayout {
- spacing: Style.marginXS * scaling
+ spacing: Style.marginXS
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
@@ -167,8 +219,8 @@ ColumnLayout {
NText {
text: (modelData.contributions || 0) + " " + ((modelData.contributions || 0) === 1 ? "commit" : "commits")
- pointSize: Style.fontSizeXS * scaling
- color: contributorArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
+ pointSize: Style.fontSizeXS
+ color: contributorArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
}
}
}
@@ -186,8 +238,4 @@ ColumnLayout {
}
}
}
-
- Item {
- Layout.fillHeight: true
- }
}
diff --git a/Modules/Settings/Tabs/AudioTab.qml b/Modules/Settings/Tabs/AudioTab.qml
index e4f70407..ef73bbdd 100644
--- a/Modules/Settings/Tabs/AudioTab.qml
+++ b/Modules/Settings/Tabs/AudioTab.qml
@@ -8,7 +8,7 @@ import qs.Widgets
ColumnLayout {
id: root
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
NHeader {
label: I18n.tr("settings.audio.volumes.section.label")
@@ -26,7 +26,7 @@ ColumnLayout {
// Master Volume
ColumnLayout {
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
Layout.fillWidth: true
NLabel {
@@ -61,7 +61,7 @@ ColumnLayout {
// Mute Toggle
ColumnLayout {
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
Layout.fillWidth: true
NToggle {
@@ -78,7 +78,7 @@ ColumnLayout {
// Input Volume
ColumnLayout {
- spacing: Style.marginXS * scaling
+ spacing: Style.marginXS
Layout.fillWidth: true
NLabel {
@@ -99,7 +99,7 @@ ColumnLayout {
// Input Mute Toggle
ColumnLayout {
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
Layout.fillWidth: true
NToggle {
@@ -112,7 +112,7 @@ ColumnLayout {
// Volume Step Size
ColumnLayout {
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
Layout.fillWidth: true
NSpinBox {
@@ -130,7 +130,7 @@ ColumnLayout {
// Raise maximum volume above 100%
ColumnLayout {
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
Layout.fillWidth: true
NToggle {
@@ -143,13 +143,13 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
// AudioService Devices
ColumnLayout {
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
Layout.fillWidth: true
NHeader {
@@ -164,9 +164,9 @@ ColumnLayout {
}
ColumnLayout {
- spacing: Style.marginXS * scaling
+ spacing: Style.marginXS
Layout.fillWidth: true
- Layout.bottomMargin: Style.marginL * scaling
+ Layout.bottomMargin: Style.marginL
NLabel {
label: I18n.tr("settings.audio.devices.output-device.label")
@@ -196,7 +196,7 @@ ColumnLayout {
}
ColumnLayout {
- spacing: Style.marginXS * scaling
+ spacing: Style.marginXS
Layout.fillWidth: true
NLabel {
@@ -222,13 +222,13 @@ ColumnLayout {
// Divider
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
// Media Player Preferences
ColumnLayout {
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
NHeader {
label: I18n.tr("settings.audio.media.section.label")
@@ -249,11 +249,11 @@ ColumnLayout {
// Blacklist editor
ColumnLayout {
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
Layout.fillWidth: true
RowLayout {
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
Layout.fillWidth: true
NTextInput {
@@ -267,7 +267,7 @@ ColumnLayout {
NIconButton {
icon: "add"
Layout.alignment: Qt.AlignBottom
- Layout.bottomMargin: blacklistInput.description ? Style.marginS * scaling : 0
+ Layout.bottomMargin: blacklistInput.description ? Style.marginS : 0
onClicked: {
const val = (blacklistInput.text || "").trim()
if (val !== "") {
@@ -285,40 +285,40 @@ ColumnLayout {
// Current blacklist entries
Flow {
Layout.fillWidth: true
- Layout.leftMargin: Style.marginS * scaling
- spacing: Style.marginS * scaling
+ Layout.leftMargin: Style.marginS
+ spacing: Style.marginS
Repeater {
model: Settings.data.audio.mprisBlacklist
delegate: Rectangle {
required property string modelData
// Padding around the inner row
- property real pad: Style.marginS * scaling
+ property real pad: Style.marginS
// Visuals
color: Qt.alpha(Color.mOnSurface, 0.125)
border.color: Qt.alpha(Color.mOnSurface, Style.opacityLight)
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
// Content
RowLayout {
id: chipRow
- spacing: Style.marginXS * scaling
+ spacing: Style.marginXS
anchors.fill: parent
anchors.margins: pad
NText {
text: modelData
color: Color.mOnSurface
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
Layout.alignment: Qt.AlignVCenter
- Layout.leftMargin: Style.marginS * scaling
+ Layout.leftMargin: Style.marginS
}
NIconButton {
icon: "close"
baseSize: Style.baseWidgetSize * 0.8
Layout.alignment: Qt.AlignVCenter
- Layout.rightMargin: Style.marginXS * scaling
+ Layout.rightMargin: Style.marginXS
onClicked: {
const arr = (Settings.data.audio.mprisBlacklist || [])
const idx = arr.findIndex(x => String(x) === modelData)
@@ -333,8 +333,8 @@ ColumnLayout {
// Intrinsic size derived from inner row + padding
implicitWidth: chipRow.implicitWidth + pad * 2
- implicitHeight: Math.max(chipRow.implicitHeight + pad * 2, Style.baseWidgetSize * 0.8 * scaling)
- radius: Style.radiusM * scaling
+ implicitHeight: Math.max(chipRow.implicitHeight + pad * 2, Style.baseWidgetSize * 0.8)
+ radius: Style.radiusM
}
}
}
@@ -407,7 +407,7 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
}
diff --git a/Modules/Settings/Tabs/BarTab.qml b/Modules/Settings/Tabs/BarTab.qml
index 31405f5c..e1a9421a 100644
--- a/Modules/Settings/Tabs/BarTab.qml
+++ b/Modules/Settings/Tabs/BarTab.qml
@@ -5,11 +5,10 @@ import Quickshell
import qs.Commons
import qs.Services
import qs.Widgets
-import qs.Modules.Settings.Bar
ColumnLayout {
id: root
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
// Helper functions to update arrays immutably
function addMonitor(list, name) {
@@ -71,6 +70,9 @@ ColumnLayout {
label: I18n.tr("settings.bar.appearance.density.label")
description: I18n.tr("settings.bar.appearance.density.description")
model: [{
+ "key": "mini",
+ "name": I18n.tr("options.bar.density.mini")
+ }, {
"key": "compact",
"name": I18n.tr("options.bar.density.compact")
}, {
@@ -84,26 +86,6 @@ ColumnLayout {
onSelected: key => Settings.data.bar.density = key
}
- ColumnLayout {
- spacing: Style.marginXXS * scaling
- Layout.fillWidth: true
-
- NLabel {
- label: I18n.tr("settings.bar.appearance.background-opacity.label")
- description: I18n.tr("settings.bar.appearance.background-opacity.description")
- }
-
- NValueSlider {
- Layout.fillWidth: true
- from: 0
- to: 1
- stepSize: 0.01
- value: Settings.data.bar.backgroundOpacity
- onMoved: value => Settings.data.bar.backgroundOpacity = value
- text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%"
- }
- }
-
NToggle {
Layout.fillWidth: true
label: I18n.tr("settings.bar.appearance.show-capsule.label")
@@ -123,7 +105,7 @@ ColumnLayout {
// Floating bar options - only show when floating is enabled
ColumnLayout {
visible: Settings.data.bar.floating
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
Layout.fillWidth: true
NLabel {
@@ -133,14 +115,14 @@ ColumnLayout {
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
ColumnLayout {
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
NText {
text: I18n.tr("settings.bar.appearance.margins.vertical")
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
}
@@ -156,11 +138,11 @@ ColumnLayout {
}
ColumnLayout {
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
NText {
text: I18n.tr("settings.bar.appearance.margins.horizontal")
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
}
@@ -177,15 +159,35 @@ ColumnLayout {
}
}
+ ColumnLayout {
+ spacing: Style.marginXXS
+ Layout.fillWidth: true
+
+ NLabel {
+ label: I18n.tr("settings.bar.appearance.background-opacity.label")
+ description: I18n.tr("settings.bar.appearance.background-opacity.description")
+ }
+
+ NValueSlider {
+ Layout.fillWidth: true
+ from: 0
+ to: 1
+ stepSize: 0.01
+ value: Settings.data.bar.backgroundOpacity
+ onMoved: value => Settings.data.bar.backgroundOpacity = value
+ text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%"
+ }
+ }
+
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
// Widgets Management Section
ColumnLayout {
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
Layout.fillWidth: true
NHeader {
@@ -197,13 +199,15 @@ ColumnLayout {
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
- Layout.topMargin: Style.marginM * scaling
- spacing: Style.marginM * scaling
+ Layout.topMargin: Style.marginM
+ spacing: Style.marginM
// Left Section
- BarSectionEditor {
+ NSectionEditor {
sectionName: "Left"
sectionId: "left"
+ settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Settings/Bar/BarWidgetSettingsDialog.qml")
+ widgetRegistry: BarWidgetRegistry
widgetModel: Settings.data.bar.widgets.left
availableWidgets: availableWidgets
onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
@@ -216,9 +220,11 @@ ColumnLayout {
}
// Center Section
- BarSectionEditor {
+ NSectionEditor {
sectionName: "Center"
sectionId: "center"
+ settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Settings/Bar/BarWidgetSettingsDialog.qml")
+ widgetRegistry: BarWidgetRegistry
widgetModel: Settings.data.bar.widgets.center
availableWidgets: availableWidgets
onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
@@ -231,9 +237,11 @@ ColumnLayout {
}
// Right Section
- BarSectionEditor {
+ NSectionEditor {
sectionName: "Right"
sectionId: "right"
+ settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Settings/Bar/BarWidgetSettingsDialog.qml")
+ widgetRegistry: BarWidgetRegistry
widgetModel: Settings.data.bar.widgets.right
availableWidgets: availableWidgets
onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
@@ -249,13 +257,13 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
// Monitor Configuration
ColumnLayout {
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
Layout.fillWidth: true
NHeader {
@@ -268,11 +276,15 @@ ColumnLayout {
delegate: NCheckbox {
Layout.fillWidth: true
label: modelData.name || "Unknown"
- description: I18n.tr("system.monitor-description", {
- "model": modelData.model,
- "width": modelData.width,
- "height": modelData.height
- })
+ description: {
+ const compositorScale = CompositorService.getDisplayScale(modelData.name)
+ I18n.tr("system.monitor-description", {
+ "model": modelData.model,
+ "width": modelData.width * compositorScale,
+ "height": modelData.height * compositorScale,
+ "scale": compositorScale
+ })
+ }
checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
@@ -287,13 +299,11 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
- // ---------------------------------
// Signal functions
- // ---------------------------------
function _addWidgetToSection(widgetId, section) {
var newWidget = {
"id": widgetId
@@ -363,19 +373,51 @@ ColumnLayout {
}
}
+ // Data model functions
+ function getWidgetLocations(widgetId) {
+ if (!BarService)
+ return []
+ const instances = BarService.getAllRegisteredWidgets()
+ const locations = {}
+ for (var i = 0; i < instances.length; i++) {
+ if (instances[i].widgetId === widgetId) {
+ const section = instances[i].section
+ if (section === "left")
+ locations["L"] = true
+ else if (section === "center")
+ locations["C"] = true
+ else if (section === "right")
+ locations["R"] = true
+ }
+ }
+ return Object.keys(locations).join('')
+ }
+
+ function updateAvailableWidgetsModel() {
+ availableWidgets.clear()
+ const widgets = BarWidgetRegistry.getAvailableWidgets()
+ widgets.forEach(entry => {
+ availableWidgets.append({
+ "key": entry,
+ "name": entry,
+ "badgeLocations": getWidgetLocations(entry)
+ })
+ })
+ }
+
// Base list model for all combo boxes
ListModel {
id: availableWidgets
}
Component.onCompleted: {
- // Fill out availableWidgets ListModel
- availableWidgets.clear()
- BarWidgetRegistry.getAvailableWidgets().forEach(entry => {
- availableWidgets.append({
- "key": entry,
- "name": entry
- })
- })
+ updateAvailableWidgetsModel()
+ }
+
+ Connections {
+ target: BarService
+ function onActiveWidgetsChanged() {
+ updateAvailableWidgetsModel()
+ }
}
}
diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml
index fa2f84fa..b38d521e 100644
--- a/Modules/Settings/Tabs/ColorSchemeTab.qml
+++ b/Modules/Settings/Tabs/ColorSchemeTab.qml
@@ -11,16 +11,16 @@ ColumnLayout {
// Cache for scheme JSON (can be flat or {dark, light})
property var schemeColorsCache: ({})
+ property int cacheVersion: 0 // Increment to trigger UI updates
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
// Helper function to extract scheme name from path
function extractSchemeName(schemePath) {
var pathParts = schemePath.split("/")
- var filename = pathParts[pathParts.length - 1] // Get filename
- var schemeName = filename.replace(".json", "") // Remove .json extension
+ var filename = pathParts[pathParts.length - 1]
+ var schemeName = filename.replace(".json", "")
- // Convert folder names back to display names
if (schemeName === "Noctalia-default") {
schemeName = "Noctalia (default)"
} else if (schemeName === "Noctalia-legacy") {
@@ -33,39 +33,52 @@ ColumnLayout {
}
// Helper function to get color from scheme file (supports dark/light variants)
- function getSchemeColor(schemePath, colorKey) {
- // Extract scheme name from path
- var schemeName = extractSchemeName(schemePath)
+ function getSchemeColor(schemeName, colorKey) {
+ // Access cache version to create dependency
+ var _ = cacheVersion
- // Try to get from cached data first
if (schemeColorsCache[schemeName]) {
var entry = schemeColorsCache[schemeName]
var variant = entry
+
+ // Check if scheme has dark/light variants
if (entry.dark || entry.light) {
variant = Settings.data.colorSchemes.darkMode ? (entry.dark || entry.light) : (entry.light || entry.dark)
}
- if (variant && variant[colorKey])
+
+ if (variant && variant[colorKey]) {
return variant[colorKey]
+ }
}
- // Return a default color if not cached yet
- return "#000000"
+ // Return visible defaults while loading
+ if (colorKey === "mSurface")
+ return Color.mSurfaceVariant
+ if (colorKey === "mPrimary")
+ return Color.mPrimary
+ if (colorKey === "mSecondary")
+ return Color.mSecondary
+ if (colorKey === "mTertiary")
+ return Color.mTertiary
+ if (colorKey === "mError")
+ return Color.mError
+ return Color.mOnSurfaceVariant
}
// This function is called by the FileView Repeater when a scheme file is loaded
function schemeLoaded(schemeName, jsonData) {
var value = jsonData || {}
- var newCache = schemeColorsCache
- newCache[schemeName] = value
- schemeColorsCache = newCache
+ schemeColorsCache[schemeName] = value
+ // Force UI update by incrementing cache version
+ cacheVersion++
}
- // When the list of available schemes changes, clear the cache.
- // The Repeater below will automatically re-create the FileViews.
+ // When the list of available schemes changes, clear the cache
Connections {
target: ColorSchemeService
function onSchemesChanged() {
schemeColorsCache = {}
+ cacheVersion++
}
}
@@ -77,12 +90,10 @@ ColumnLayout {
onExited: function (exitCode) {
if (exitCode === 0) {
- // Matugen exists, enable it
Settings.data.colorSchemes.useWallpaperColors = true
AppThemeService.generate()
ToastService.showNotice(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.enabled"))
} else {
- // Matugen not found
ToastService.showWarning(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.not-installed"))
}
}
@@ -91,7 +102,7 @@ ColumnLayout {
stderr: StdioCollector {}
}
- // A non-visual Item to host the Repeater that loads the color scheme files.
+ // A non-visual Item to host the Repeater that loads the color scheme files
Item {
visible: false
id: fileLoaders
@@ -99,21 +110,19 @@ ColumnLayout {
Repeater {
model: ColorSchemeService.schemes
- // The delegate is a Component, which correctly wraps the non-visual FileView
delegate: Item {
FileView {
path: modelData
- blockLoading: true
+ blockLoading: false
onLoaded: {
- // Extract scheme name from path
- var schemeName = extractSchemeName(path)
+ var schemeName = root.extractSchemeName(path)
try {
var jsonData = JSON.parse(text())
root.schemeLoaded(schemeName, jsonData)
} catch (e) {
Logger.warn("ColorSchemeTab", "Failed to parse JSON for scheme:", schemeName, e)
- root.schemeLoaded(schemeName, null) // Load defaults on parse error
+ root.schemeLoaded(schemeName, null)
}
}
}
@@ -127,13 +136,16 @@ ColumnLayout {
description: I18n.tr("settings.color-scheme.color-source.section.description")
}
- // Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants)
+ // Dark Mode Toggle
NToggle {
label: I18n.tr("settings.color-scheme.color-source.dark-mode.label")
description: I18n.tr("settings.color-scheme.color-source.dark-mode.description")
checked: Settings.data.colorSchemes.darkMode
enabled: true
- onToggled: checked => Settings.data.colorSchemes.darkMode = checked
+ onToggled: checked => {
+ Settings.data.colorSchemes.darkMode = checked
+ root.cacheVersion++ // Force UI update for dark/light variants
+ }
}
// Use Wallpaper Colors
@@ -143,14 +155,12 @@ ColumnLayout {
checked: Settings.data.colorSchemes.useWallpaperColors
onToggled: checked => {
if (checked) {
- // Check if matugen is installed
matugenCheck.running = true
} else {
Settings.data.colorSchemes.useWallpaperColors = false
ToastService.showNotice(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.disabled"))
if (Settings.data.colorSchemes.predefinedScheme) {
-
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
}
}
@@ -195,21 +205,20 @@ ColumnLayout {
onSelected: key => {
Settings.data.colorSchemes.matugenSchemeType = key
-
AppThemeService.generate()
}
}
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
visible: !Settings.data.colorSchemes.useWallpaperColors
}
// Predefined Color Schemes
ColumnLayout {
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
Layout.fillWidth: true
visible: !Settings.data.colorSchemes.useWallpaperColors
@@ -220,9 +229,9 @@ ColumnLayout {
// Color Schemes Grid
GridLayout {
- columns: Math.max(1, Math.floor((root.width + columnSpacing) / (222 * scaling + columnSpacing)))
- rowSpacing: Style.marginM * scaling
- columnSpacing: Style.marginM * scaling
+ columns: 3
+ rowSpacing: Style.marginM
+ columnSpacing: Style.marginM
Layout.fillWidth: true
Repeater {
@@ -232,15 +241,16 @@ ColumnLayout {
id: schemeItem
property string schemePath: modelData
+ property string schemeName: root.extractSchemeName(modelData)
+ Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
- width: 222 * scaling
- height: 50 * scaling
- radius: Style.radiusS * scaling
- color: getSchemeColor(modelData, "mSurface")
- border.width: Math.max(1, Style.borderL * scaling)
+ height: 50
+ radius: Style.radiusS
+ color: root.getSchemeColor(schemeName, "mSurface")
+ border.width: Math.max(1, Style.borderL)
border.color: {
- if (Settings.data.colorSchemes.predefinedScheme === extractSchemeName(modelData)) {
+ if (Settings.data.colorSchemes.predefinedScheme === schemeName) {
return Color.mSecondary
}
if (itemMouseArea.containsMouse) {
@@ -251,16 +261,15 @@ ColumnLayout {
RowLayout {
anchors.fill: parent
- anchors.margins: Style.marginM * scaling
- spacing: Style.marginXS * scaling
+ anchors.margins: Style.marginL
+ spacing: Style.marginXS
NText {
- text: extractSchemeName(schemePath)
- pointSize: Style.fontSizeS * scaling
+ text: schemeItem.schemeName
+ pointSize: Style.fontSizeS
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
Layout.fillWidth: true
- // Layout.maximumWidth: 150 * scaling
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
@@ -268,31 +277,31 @@ ColumnLayout {
}
Rectangle {
- width: 14 * scaling
- height: 14 * scaling
+ width: 14
+ height: 14
radius: width * 0.5
- color: getSchemeColor(modelData, "mPrimary")
+ color: root.getSchemeColor(schemeItem.schemeName, "mPrimary")
}
Rectangle {
- width: 14 * scaling
- height: 14 * scaling
+ width: 14
+ height: 14
radius: width * 0.5
- color: getSchemeColor(modelData, "mSecondary")
+ color: root.getSchemeColor(schemeItem.schemeName, "mSecondary")
}
Rectangle {
- width: 14 * scaling
- height: 14 * scaling
+ width: 14
+ height: 14
radius: width * 0.5
- color: getSchemeColor(modelData, "mTertiary")
+ color: root.getSchemeColor(schemeItem.schemeName, "mTertiary")
}
Rectangle {
- width: 14 * scaling
- height: 14 * scaling
+ width: 14
+ height: 14
radius: width * 0.5
- color: getSchemeColor(modelData, "mError")
+ color: root.getSchemeColor(schemeItem.schemeName, "mError")
}
}
@@ -305,28 +314,28 @@ ColumnLayout {
Settings.data.colorSchemes.useWallpaperColors = false
Logger.log("ColorSchemeTab", "Disabled wallpaper colors")
- Settings.data.colorSchemes.predefinedScheme = extractSchemeName(schemePath)
+ Settings.data.colorSchemes.predefinedScheme = schemeItem.schemeName
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
}
}
// Selection indicator
Rectangle {
- visible: (Settings.data.colorSchemes.predefinedScheme === extractSchemeName(schemePath))
+ visible: (Settings.data.colorSchemes.predefinedScheme === schemeItem.schemeName)
anchors.right: parent.right
anchors.top: parent.top
- anchors.rightMargin: -3 * scaling
- anchors.topMargin: -3 * scaling
- width: 20 * scaling
- height: 20 * scaling
+ anchors.rightMargin: -3
+ anchors.topMargin: -3
+ width: 20
+ height: 20
radius: width * 0.5
color: Color.mSecondary
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
border.color: Color.mOnSecondary
NIcon {
icon: "check"
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
font.weight: Style.fontWeightBold
color: Color.mOnSecondary
anchors.centerIn: parent
@@ -350,25 +359,24 @@ ColumnLayout {
checked: Settings.data.colorSchemes.generateTemplatesForPredefined
onToggled: checked => {
Settings.data.colorSchemes.generateTemplatesForPredefined = checked
- // Re-generate templates if a predefined scheme is currently active
if (!Settings.data.colorSchemes.useWallpaperColors && Settings.data.colorSchemes.predefinedScheme) {
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
}
}
- Layout.topMargin: Style.marginL * scaling
+ Layout.topMargin: Style.marginL
}
}
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
// Template toggles organized by category
ColumnLayout {
Layout.fillWidth: true
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
NHeader {
label: I18n.tr("settings.color-scheme.templates.section.label")
@@ -405,6 +413,18 @@ ColumnLayout {
AppThemeService.generate()
}
}
+
+ NCheckbox {
+ label: "KColorScheme"
+ description: I18n.tr("settings.color-scheme.templates.ui.kcolorscheme.description", {
+ "filepath": "~/.local/share/color-schemes/noctalia.colors"
+ })
+ checked: Settings.data.templates.kcolorscheme
+ onToggled: checked => {
+ Settings.data.templates.kcolorscheme = checked
+ AppThemeService.generate()
+ }
+ }
}
// Terminal Emulators
@@ -494,22 +514,29 @@ ColumnLayout {
}
}
- NCheckbox {
- label: "Vesktop"
- description: ProgramCheckerService.vesktopAvailable ? I18n.tr("settings.color-scheme.templates.programs.vesktop.description", {
- "filepath": "~/.config/vesktop/themes/noctalia.theme.css"
- }) : I18n.tr("settings.color-scheme.templates.programs.vesktop.description-missing", {
- "app": "vesktop"
- })
- checked: Settings.data.templates.vesktop
- enabled: ProgramCheckerService.vesktopAvailable
- opacity: ProgramCheckerService.vesktopAvailable ? 1.0 : 0.6
- onToggled: checked => {
- if (ProgramCheckerService.vesktopAvailable) {
- Settings.data.templates.vesktop = checked
+ // Show individual checkboxes for each detected Discord client
+ Repeater {
+ model: ProgramCheckerService.availableDiscordClients
+ delegate: NCheckbox {
+ label: modelData.name.charAt(0).toUpperCase() + modelData.name.slice(1)
+ description: I18n.tr("settings.color-scheme.templates.programs.discord.description", {
+ "client": modelData.name.charAt(0).toUpperCase() + modelData.name.slice(1),
+ "filepath": modelData.themePath
+ })
+ checked: Settings.data.templates["discord_" + modelData.name] || false
+ onToggled: checked => {
+ Settings.data.templates["discord_" + modelData.name] = checked
AppThemeService.generate()
}
- }
+ }
+ }
+
+ // Show message if no Discord clients detected
+ NText {
+ visible: ProgramCheckerService.availableDiscordClients.length === 0
+ text: I18n.tr("settings.color-scheme.templates.programs.discord.description-missing")
+ color: Color.mOnSurfaceVariant
+ pointSize: Style.fontSizeS
}
NCheckbox {
diff --git a/Modules/Settings/Tabs/ControlCenterTab.qml b/Modules/Settings/Tabs/ControlCenterTab.qml
new file mode 100644
index 00000000..4bf62cfb
--- /dev/null
+++ b/Modules/Settings/Tabs/ControlCenterTab.qml
@@ -0,0 +1,337 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import qs.Commons
+import qs.Services
+import qs.Widgets
+
+ColumnLayout {
+ id: root
+ spacing: Style.marginL
+
+ property list cardsModel: []
+ property list cardsDefault: [{
+ "id": "profile-card",
+ "text": "Profile",
+ "enabled": true,
+ "required": true
+ }, {
+ "id": "shortcuts-card",
+ "text": "Shortcuts",
+ "enabled": true,
+ "required": false
+ }, {
+ "id": "audio-card",
+ "text": "Audio Sliders",
+ "enabled": true,
+ "required": false
+ }, {
+ "id": "weather-card",
+ "text": "Weather",
+ "enabled": true,
+ "required": false
+ }, {
+ "id": "media-sysmon-card",
+ "text": "Media and System Monitor",
+ "enabled": true,
+ "required": false
+ }]
+
+ // Handler for drag start - disables panel background clicks
+ function handleDragStart() {
+ var panel = PanelService.getPanel("settingsPanel")
+ if (panel && panel.disableBackgroundClick) {
+ panel.disableBackgroundClick()
+ }
+ }
+
+ // Handler for drag end - re-enables panel background clicks
+ function handleDragEnd() {
+ var panel = PanelService.getPanel("settingsPanel")
+ if (panel && panel.enableBackgroundClick) {
+ panel.enableBackgroundClick()
+ }
+ }
+
+ function saveCards() {
+ var toSave = []
+ for (var i = 0; i < cardsModel.length; i++) {
+ toSave.push({
+ "id": cardsModel[i].id,
+ "enabled": cardsModel[i].enabled
+ })
+ }
+ Settings.data.controlCenter.cards = toSave
+ }
+
+ Component.onCompleted: {
+ // Fill out availableWidgets ListModel
+ availableWidgets.clear()
+ ControlCenterWidgetRegistry.getAvailableWidgets().forEach(entry => {
+ availableWidgets.append({
+ "key": entry,
+ "name": entry
+ })
+ })
+ // Starts empty
+ cardsModel = []
+
+ // Add the cards available in settings
+ for (var i = 0; i < Settings.data.controlCenter.cards.length; i++) {
+ const settingCard = Settings.data.controlCenter.cards[i]
+
+ for (var j = 0; j < cardsDefault.length; j++) {
+ if (settingCard.id === cardsDefault[j].id) {
+ var card = cardsDefault[j]
+ card.enabled = settingCard.enabled
+ 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) {
+ cardsModel.push(cardsDefault[i])
+ }
+ }
+
+ saveCards()
+ }
+
+ ColumnLayout {
+ spacing: Style.marginL
+ Layout.fillWidth: true
+
+ NHeader {
+ label: I18n.tr("settings.control-center.section.label")
+ description: I18n.tr("settings.control-center.section.description")
+ }
+
+ NComboBox {
+ id: controlCenterPosition
+ label: I18n.tr("settings.control-center.position.label")
+ description: I18n.tr("settings.control-center.position.description")
+ Layout.fillWidth: true
+ model: [{
+ "key": "close_to_bar_button",
+ "name": I18n.tr("options.control-center.position.close_to_bar_button")
+ }, {
+ "key": "top_left",
+ "name": I18n.tr("options.control-center.position.top_left")
+ }, {
+ "key": "top_right",
+ "name": I18n.tr("options.control-center.position.top_right")
+ }, {
+ "key": "bottom_left",
+ "name": I18n.tr("options.control-center.position.bottom_left")
+ }, {
+ "key": "bottom_right",
+ "name": I18n.tr("options.control-center.position.bottom_right")
+ }, {
+ "key": "bottom_center",
+ "name": I18n.tr("options.control-center.position.bottom_center")
+ }, {
+ "key": "top_center",
+ "name": I18n.tr("options.control-center.position.top_center")
+ }]
+ currentKey: Settings.data.controlCenter.position
+ onSelected: function (key) {
+ Settings.data.controlCenter.position = key
+ }
+ }
+ }
+
+ NDivider {
+ Layout.fillWidth: true
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
+ }
+
+ // Widgets Management Section
+ ColumnLayout {
+ spacing: Style.marginXXS
+ Layout.fillWidth: true
+
+ NHeader {
+ label: I18n.tr("settings.control-center.cards.section.label")
+ description: I18n.tr("settings.control-center.cards.section.description")
+ }
+
+ NReorderCheckboxes {
+ Layout.fillWidth: true
+ model: cardsModel
+ onDragPotentialStarted: {
+ root.handleDragStart()
+ }
+ onDragPotentialEnded: {
+ root.handleDragEnd()
+ }
+ onItemToggled: function (index, enabled) {
+ //Logger.log("ControlCenterTab", "Item", index, "toggled to", enabled)
+ var newModel = cardsModel.slice()
+ newModel[index] = Object.assign({}, newModel[index], {
+ "enabled": enabled
+ })
+ cardsModel = newModel
+ saveCards()
+ }
+ onItemsReordered: function (fromIndex, toIndex) {
+ //Logger.log("ControlCenterTab", "Item moved from", fromIndex, "to", 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.marginXL
+ Layout.bottomMargin: Style.marginXL
+ }
+
+ // Widgets Management Section
+ ColumnLayout {
+ spacing: Style.marginXXS
+ Layout.fillWidth: true
+
+ NHeader {
+ label: I18n.tr("settings.control-center.shortcuts.section.label")
+ description: I18n.tr("settings.control-center.shortcuts.section.description")
+ }
+
+ // Sections
+ ColumnLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.topMargin: Style.marginM
+ spacing: Style.marginM
+
+ // Left
+ NSectionEditor {
+ sectionName: I18n.tr("settings.control-center.shortcuts.sectionLeft")
+ sectionId: "left"
+ settingsDialogComponent: ""
+ maxWidgets: 5
+ widgetRegistry: ControlCenterWidgetRegistry
+ widgetModel: Settings.data.controlCenter.shortcuts["left"]
+ availableWidgets: availableWidgets
+ availableSections: ["left", "right"]
+ onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
+ onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index)
+ onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
+ onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
+ onMoveWidget: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection)
+ onDragPotentialStarted: root.handleDragStart()
+ onDragPotentialEnded: root.handleDragEnd()
+ }
+
+ // Right
+ NSectionEditor {
+ sectionName: I18n.tr("settings.control-center.shortcuts.sectionRight")
+ sectionId: "right"
+ settingsDialogComponent: ""
+ maxWidgets: 5
+ widgetRegistry: ControlCenterWidgetRegistry
+ widgetModel: Settings.data.controlCenter.shortcuts["right"]
+ availableWidgets: availableWidgets
+ availableSections: ["left", "right"]
+ onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
+ onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index)
+ onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
+ onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
+ onMoveWidget: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection)
+ onDragPotentialStarted: root.handleDragStart()
+ onDragPotentialEnded: root.handleDragEnd()
+ }
+ }
+ }
+
+ NDivider {
+ Layout.fillWidth: true
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
+ }
+
+ // ---------------------------------
+ // Signal functions
+ // ---------------------------------
+ function _addWidgetToSection(widgetId, section) {
+ var newWidget = {
+ "id": widgetId
+ }
+ if (ControlCenterWidgetRegistry.widgetHasUserSettings(widgetId)) {
+ var metadata = ControlCenterWidgetRegistry.widgetMetadata[widgetId]
+ if (metadata) {
+ Object.keys(metadata).forEach(function (key) {
+ if (key !== "allowUserSettings") {
+ newWidget[key] = metadata[key]
+ }
+ })
+ }
+ }
+ Settings.data.controlCenter.shortcuts[section].push(newWidget)
+ }
+
+ function _removeWidgetFromSection(section, index) {
+ if (index >= 0 && index < Settings.data.controlCenter.shortcuts[section].length) {
+ var newArray = Settings.data.controlCenter.shortcuts[section].slice()
+ var removedWidgets = newArray.splice(index, 1)
+ Settings.data.controlCenter.shortcuts[section] = newArray
+ }
+ }
+
+ function _reorderWidgetInSection(section, fromIndex, toIndex) {
+ if (fromIndex >= 0 && fromIndex < Settings.data.controlCenter.shortcuts[section].length && toIndex >= 0 && toIndex < Settings.data.controlCenter.shortcuts[section].length) {
+
+ // Create a new array to avoid modifying the original
+ var newArray = Settings.data.controlCenter.shortcuts[section].slice()
+ var item = newArray[fromIndex]
+ newArray.splice(fromIndex, 1)
+ newArray.splice(toIndex, 0, item)
+
+ Settings.data.controlCenter.shortcuts[section] = newArray
+ }
+ }
+
+ function _moveWidgetBetweenSections(fromSection, index, toSection) {
+ // Get the widget from the source section
+ if (index >= 0 && index < Settings.data.controlCenter.shortcuts[fromSection].length) {
+ var widget = Settings.data.controlCenter.shortcuts[fromSection][index]
+
+ // Remove from source section
+ var sourceArray = Settings.data.controlCenter.shortcuts[fromSection].slice()
+ sourceArray.splice(index, 1)
+ Settings.data.controlCenter.shortcuts[fromSection] = sourceArray
+
+ // Add to target section
+ var targetArray = Settings.data.controlCenter.shortcuts[toSection].slice()
+ targetArray.push(widget)
+ Settings.data.controlCenter.shortcuts[toSection] = targetArray
+
+ //Logger.log("BarTab", `Moved widget ${widget.id} from ${fromSection} to ${toSection}`)
+ }
+ }
+
+ function _updateWidgetSettingsInSection(section, index, settings) {
+ // Update the widget settings in the Settings data
+ Settings.data.controlCenter.shortcuts[section][index] = settings
+ }
+
+ // Base list model for all combo boxes
+ ListModel {
+ id: availableWidgets
+ }
+}
diff --git a/Modules/Settings/Tabs/DisplayTab.qml b/Modules/Settings/Tabs/DisplayTab.qml
index 491dacea..d1823fa1 100644
--- a/Modules/Settings/Tabs/DisplayTab.qml
+++ b/Modules/Settings/Tabs/DisplayTab.qml
@@ -49,7 +49,7 @@ ColumnLayout {
stderr: StdioCollector {}
}
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
NHeader {
label: I18n.tr("settings.display.monitors.section.label")
@@ -57,107 +57,53 @@ ColumnLayout {
}
ColumnLayout {
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Repeater {
model: Quickshell.screens || []
delegate: Rectangle {
Layout.fillWidth: true
- implicitHeight: contentCol.implicitHeight + Style.marginL * 2 * scaling
- radius: Style.radiusM * scaling
+ implicitHeight: contentCol.implicitHeight + Style.marginL * 2
+ radius: Style.radiusM
color: Color.mSurfaceVariant
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
- property real localScaling: ScalingService.getScreenScale(modelData)
property var brightnessMonitor: BrightnessService.getMonitorForScreen(modelData)
- Connections {
- target: ScalingService
- function onScaleChanged(screenName, scale) {
- if (screenName === modelData.name) {
- localScaling = scale
- }
- }
- }
-
ColumnLayout {
id: contentCol
- width: parent.width - 2 * Style.marginL * scaling
- x: Style.marginL * scaling
- y: Style.marginL * scaling
- spacing: Style.marginXXS * scaling
+ width: parent.width - 2 * Style.marginL
+ x: Style.marginL
+ y: Style.marginL
+ spacing: Style.marginXXS
NLabel {
label: modelData.name || "Unknown"
- description: I18n.tr("system.monitor-description", {
- "model": modelData.model,
- "width": modelData.width,
- "height": modelData.height
- })
- }
-
- // Scale
- ColumnLayout {
- spacing: Style.marginS * scaling
- Layout.fillWidth: true
-
- RowLayout {
- spacing: Style.marginL * scaling
- Layout.fillWidth: true
-
- NText {
- text: I18n.tr("settings.display.monitors.scale")
- Layout.preferredWidth: 90 * scaling
- Layout.alignment: Qt.AlignVCenter
- }
-
- NValueSlider {
- id: scaleSlider
- from: 0.7
- to: 1.8
- stepSize: 0.01
- value: localScaling
- onPressedChanged: (pressed, value) => ScalingService.setScreenScale(modelData, value)
- Layout.fillWidth: true
- }
-
- NText {
- text: I18n.tr("system.scaling-percentage", {
- "percentage": Math.round(scaleSlider.value * 100)
- })
- Layout.preferredWidth: 55 * scaling
- horizontalAlignment: Text.AlignRight
- Layout.alignment: Qt.AlignVCenter
- }
-
- Item {
- Layout.preferredWidth: 30 * scaling
- Layout.fillHeight: true
- NIconButton {
- icon: "refresh"
- baseSize: Style.baseWidgetSize * 0.8
- tooltipText: I18n.tr("settings.display.monitors.reset-scaling")
- onClicked: ScalingService.setScreenScale(modelData, 1.0)
- anchors.centerIn: parent
- }
- }
+ description: {
+ const compositorScale = CompositorService.getDisplayScale(modelData.name)
+ I18n.tr("system.monitor-description", {
+ "model": modelData.model,
+ "width": modelData.width * compositorScale,
+ "height": modelData.height * compositorScale,
+ "scale": compositorScale
+ })
}
}
// Brightness
ColumnLayout {
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
Layout.fillWidth: true
visible: brightnessMonitor !== undefined && brightnessMonitor !== null
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
NText {
text: I18n.tr("settings.display.monitors.brightness")
- Layout.preferredWidth: 90 * scaling
+ Layout.preferredWidth: 90
Layout.alignment: Qt.AlignVCenter
}
@@ -167,19 +113,24 @@ ColumnLayout {
to: 1
value: brightnessMonitor ? brightnessMonitor.brightness : 0.5
stepSize: 0.01
+ onMoved: value => {
+ if (brightnessMonitor.method === "internal") {
+ brightnessMonitor.setBrightness(value)
+ }
+ }
onPressedChanged: (pressed, value) => brightnessMonitor.setBrightness(value)
Layout.fillWidth: true
}
NText {
text: brightnessMonitor ? Math.round(brightnessSlider.value * 100) + "%" : "N/A"
- Layout.preferredWidth: 55 * scaling
+ Layout.preferredWidth: 55
horizontalAlignment: Text.AlignRight
Layout.alignment: Qt.AlignVCenter
}
Item {
- Layout.preferredWidth: 30 * scaling
+ Layout.preferredWidth: 30
Layout.fillHeight: true
NIcon {
icon: brightnessMonitor.method == "internal" ? "device-laptop" : "device-desktop"
@@ -208,13 +159,13 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
// Night Light Section
ColumnLayout {
- spacing: Style.marginXS * scaling
+ spacing: Style.marginXS
Layout.fillWidth: true
NHeader {
@@ -242,7 +193,7 @@ ColumnLayout {
// Temperature
ColumnLayout {
- spacing: Style.marginXS * scaling
+ spacing: Style.marginXS
Layout.alignment: Qt.AlignVCenter
NLabel {
@@ -252,14 +203,14 @@ ColumnLayout {
RowLayout {
visible: Settings.data.nightLight.enabled
- spacing: Style.marginM * scaling
+ 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 * scaling
+ pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
}
@@ -281,7 +232,7 @@ ColumnLayout {
NText {
text: I18n.tr("settings.display.night-light.temperature.day")
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
}
@@ -314,7 +265,7 @@ ColumnLayout {
// Manual scheduling
ColumnLayout {
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
visible: Settings.data.nightLight.enabled && !Settings.data.nightLight.autoSchedule && !Settings.data.nightLight.forced
NLabel {
@@ -324,11 +275,11 @@ ColumnLayout {
RowLayout {
Layout.fillWidth: false
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
NText {
text: I18n.tr("settings.display.night-light.manual-schedule.sunrise")
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
}
@@ -337,16 +288,16 @@ ColumnLayout {
currentKey: Settings.data.nightLight.manualSunrise
placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-start")
onSelected: key => Settings.data.nightLight.manualSunrise = key
- minimumWidth: 120 * scaling
+ minimumWidth: 120
}
Item {
- Layout.preferredWidth: 20 * scaling
+ Layout.preferredWidth: 20
}
NText {
text: I18n.tr("settings.display.night-light.manual-schedule.sunset")
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
}
@@ -355,7 +306,7 @@ ColumnLayout {
currentKey: Settings.data.nightLight.manualSunset
placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-stop")
onSelected: key => Settings.data.nightLight.manualSunset = key
- minimumWidth: 120 * scaling
+ minimumWidth: 120
}
}
}
@@ -379,7 +330,7 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
}
diff --git a/Modules/Settings/Tabs/DockTab.qml b/Modules/Settings/Tabs/DockTab.qml
index 60ad8a9f..3203446e 100644
--- a/Modules/Settings/Tabs/DockTab.qml
+++ b/Modules/Settings/Tabs/DockTab.qml
@@ -9,7 +9,7 @@ import qs.Widgets
ColumnLayout {
id: root
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
// Helper functions to update arrays immutably
function addMonitor(list, name) {
@@ -29,34 +29,28 @@ ColumnLayout {
description: I18n.tr("settings.dock.appearance.section.description")
}
- ColumnLayout {
- spacing: Style.marginXXS * scaling
+ NComboBox {
Layout.fillWidth: true
- NLabel {
- label: I18n.tr("settings.dock.appearance.display.label")
- description: I18n.tr("settings.dock.appearance.display.description")
- }
- NComboBox {
- Layout.fillWidth: true
- model: [{
- "key": "always_visible",
- "name": I18n.tr("settings.dock.appearance.display.always-visible")
- }, {
- "key": "auto_hide",
- "name": I18n.tr("settings.dock.appearance.display.auto-hide")
- }, {
- "key": "exclusive",
- "name": I18n.tr("settings.dock.appearance.display.exclusive")
- }]
- currentKey: Settings.data.dock.displayMode
- onSelected: key => {
- Settings.data.dock.displayMode = key
- }
- }
+ label: I18n.tr("settings.dock.appearance.display.label")
+ description: I18n.tr("settings.dock.appearance.display.description")
+ model: [{
+ "key": "always_visible",
+ "name": I18n.tr("settings.dock.appearance.display.always-visible")
+ }, {
+ "key": "auto_hide",
+ "name": I18n.tr("settings.dock.appearance.display.auto-hide")
+ }, {
+ "key": "exclusive",
+ "name": I18n.tr("settings.dock.appearance.display.exclusive")
+ }]
+ currentKey: Settings.data.dock.displayMode
+ onSelected: key => {
+ Settings.data.dock.displayMode = key
+ }
}
ColumnLayout {
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
Layout.fillWidth: true
NLabel {
label: I18n.tr("settings.dock.appearance.background-opacity.label")
@@ -74,7 +68,7 @@ ColumnLayout {
}
ColumnLayout {
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
Layout.fillWidth: true
NLabel {
@@ -93,15 +87,30 @@ ColumnLayout {
}
}
+ NToggle {
+ label: I18n.tr("settings.dock.monitors.only-same-output.label")
+ description: I18n.tr("settings.dock.monitors.only-same-output.description")
+ checked: Settings.data.dock.onlySameOutput
+ onToggled: checked => Settings.data.dock.onlySameOutput = checked
+ }
+
+ NToggle {
+ Layout.fillWidth: true
+ label: I18n.tr("settings.dock.appearance.colorize-icons.label")
+ description: I18n.tr("settings.dock.appearance.colorize-icons.description")
+ checked: Settings.data.dock.colorizeIcons
+ onToggled: checked => Settings.data.dock.colorizeIcons = checked
+ }
+
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
// Monitor Configuration
ColumnLayout {
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
Layout.fillWidth: true
NHeader {
@@ -114,11 +123,15 @@ ColumnLayout {
delegate: NCheckbox {
Layout.fillWidth: true
label: modelData.name || "Unknown"
- description: I18n.tr("system.monitor-description", {
- "model": modelData.model,
- "width": modelData.width,
- "height": modelData.height
- })
+ description: {
+ const compositorScale = CompositorService.getDisplayScale(modelData.name)
+ I18n.tr("system.monitor-description", {
+ "model": modelData.model,
+ "width": modelData.width * compositorScale,
+ "height": modelData.height * compositorScale,
+ "scale": compositorScale
+ })
+ }
checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
@@ -131,16 +144,9 @@ ColumnLayout {
}
}
- NToggle {
- label: I18n.tr("settings.dock.monitors.only-same-output.label")
- description: I18n.tr("settings.dock.monitors.only-same-output.description")
- checked: Settings.data.dock.onlySameOutput
- onToggled: checked => Settings.data.dock.onlySameOutput = checked
- }
-
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
}
diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml
index 4f9183a1..c4554145 100644
--- a/Modules/Settings/Tabs/GeneralTab.qml
+++ b/Modules/Settings/Tabs/GeneralTab.qml
@@ -17,16 +17,16 @@ ColumnLayout {
// Profile section
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
// Avatar preview
NImageCircled {
- width: 108 * scaling
- height: 108 * scaling
- imagePath: Settings.data.general.avatarImage
+ Layout.preferredWidth: 88 * Style.uiScaleRatio
+ Layout.preferredHeight: width
+ imagePath: Settings.preprocessPath(Settings.data.general.avatarImage)
fallbackIcon: "person"
borderColor: Color.mPrimary
- borderWidth: Math.max(1, Style.borderM * scaling)
+ borderWidth: Math.max(1, Style.borderM)
Layout.alignment: Qt.AlignTop
}
@@ -50,7 +50,7 @@ ColumnLayout {
id: avatarPicker
title: I18n.tr("settings.general.profile.select-avatar")
selectionMode: "files"
- initialPath: Settings.data.general.avatarImage.substr(0, Settings.data.general.avatarImage.lastIndexOf("/")) || Quickshell.env("HOME")
+ initialPath: Settings.preprocessPath(Settings.data.general.avatarImage).substr(0, Settings.preprocessPath(Settings.data.general.avatarImage).lastIndexOf("/")) || Quickshell.env("HOME")
nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"]
onAccepted: paths => {
if (paths.length > 0) {
@@ -61,199 +61,13 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
- }
-
- // User Interface
- ColumnLayout {
- spacing: Style.marginL * scaling
- Layout.fillWidth: true
-
- NHeader {
- label: I18n.tr("settings.general.ui.section.label")
- description: I18n.tr("settings.general.ui.section.description")
- }
-
- NToggle {
- label: I18n.tr("settings.general.ui.dim-desktop.label")
- description: I18n.tr("settings.general.ui.dim-desktop.description")
- checked: Settings.data.general.dimDesktop
- onToggled: checked => Settings.data.general.dimDesktop = checked
- }
-
- NToggle {
- label: I18n.tr("settings.general.ui.tooltips.label")
- description: I18n.tr("settings.general.ui.tooltips.description")
- checked: Settings.data.ui.tooltipsEnabled
- onToggled: checked => Settings.data.ui.tooltipsEnabled = checked
- }
-
- ColumnLayout {
- spacing: Style.marginXXS * scaling
- Layout.fillWidth: true
-
- NLabel {
- label: I18n.tr("settings.general.ui.border-radius.label")
- description: I18n.tr("settings.general.ui.border-radius.description")
- }
-
- NValueSlider {
- Layout.fillWidth: true
- from: 0
- to: 1
- stepSize: 0.01
- value: Settings.data.general.radiusRatio
- onMoved: value => Settings.data.general.radiusRatio = value
- text: Math.floor(Settings.data.general.radiusRatio * 100) + "%"
- }
- }
-
- // Animation Speed
- ColumnLayout {
- spacing: Style.marginL * scaling
- Layout.fillWidth: true
-
- NToggle {
- label: I18n.tr("settings.general.ui.animation-disable.label")
- description: I18n.tr("settings.general.ui.animation-disable.description")
- checked: Settings.data.general.animationDisabled
- onToggled: checked => Settings.data.general.animationDisabled = checked
- }
-
- ColumnLayout {
- spacing: Style.marginXXS * scaling
- Layout.fillWidth: true
- visible: !Settings.data.general.animationDisabled
-
- NLabel {
- label: I18n.tr("settings.general.ui.animation-speed.label")
- description: I18n.tr("settings.general.ui.animation-speed.description")
- }
-
- NValueSlider {
- Layout.fillWidth: true
- from: 0.1
- to: 2.0
- stepSize: 0.01
- value: Settings.data.general.animationSpeed
- onMoved: value => Settings.data.general.animationSpeed = value
- text: Math.round(Settings.data.general.animationSpeed * 100) + "%"
- }
- }
- }
- }
-
- NDivider {
- Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
- }
-
- // Dock
- ColumnLayout {
- spacing: Style.marginL * scaling
- Layout.fillWidth: true
-
- NHeader {
- label: I18n.tr("settings.general.screen-corners.section.label")
- description: I18n.tr("settings.general.screen-corners.section.description")
- }
-
- NToggle {
- label: I18n.tr("settings.general.screen-corners.show-corners.label")
- description: I18n.tr("settings.general.screen-corners.show-corners.description")
- checked: Settings.data.general.showScreenCorners
- onToggled: checked => Settings.data.general.showScreenCorners = checked
- }
-
- NToggle {
- label: I18n.tr("settings.general.screen-corners.solid-black.label")
- description: I18n.tr("settings.general.screen-corners.solid-black.description")
- checked: Settings.data.general.forceBlackScreenCorners
- onToggled: checked => Settings.data.general.forceBlackScreenCorners = checked
- }
-
- ColumnLayout {
- spacing: Style.marginXXS * scaling
- Layout.fillWidth: true
-
- NLabel {
- label: I18n.tr("settings.general.screen-corners.radius.label")
- description: I18n.tr("settings.general.screen-corners.radius.description")
- }
-
- NValueSlider {
- Layout.fillWidth: true
- from: 0
- to: 2
- stepSize: 0.01
- value: Settings.data.general.screenRadiusRatio
- onMoved: value => Settings.data.general.screenRadiusRatio = value
- text: Math.floor(Settings.data.general.screenRadiusRatio * 100) + "%"
- }
- }
- }
-
- NDivider {
- Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
- }
-
- // Control Center
- ColumnLayout {
- spacing: Style.marginL * scaling
- Layout.fillWidth: true
-
- NHeader {
- label: I18n.tr("settings.general.control-center.section.label")
- description: I18n.tr("settings.general.control-center.section.description")
- }
-
- NComboBox {
- id: controlCenterPosition
- label: I18n.tr("settings.general.control-center.position.label")
- description: I18n.tr("settings.general.control-center.position.description")
- Layout.fillWidth: true
- model: [{
- "key": "close_to_bar_button",
- "name": I18n.tr("options.control-center.position.close_to_bar_button")
- }, {
- "key": "top_left",
- "name": I18n.tr("options.control-center.position.top_left")
- }, {
- "key": "top_right",
- "name": I18n.tr("options.control-center.position.top_right")
- }, {
- "key": "bottom_left",
- "name": I18n.tr("options.control-center.position.bottom_left")
- }, {
- "key": "bottom_right",
- "name": I18n.tr("options.control-center.position.bottom_right")
- }, {
- "key": "bottom_center",
- "name": I18n.tr("options.control-center.position.bottom_center")
- }, {
- "key": "top_center",
- "name": I18n.tr("options.control-center.position.top_center")
- }]
- currentKey: Settings.data.controlCenter.position
- onSelected: function (key) {
- Settings.data.controlCenter.position = key
- }
- }
- }
-
- NDivider {
- Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
// Fonts
ColumnLayout {
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
NHeader {
@@ -263,7 +77,7 @@ ColumnLayout {
// Font configuration section
ColumnLayout {
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
NSearchableComboBox {
@@ -273,8 +87,8 @@ ColumnLayout {
currentKey: Settings.data.ui.fontDefault
placeholder: I18n.tr("settings.general.fonts.default.placeholder")
searchPlaceholder: I18n.tr("settings.general.fonts.default.search-placeholder")
- popupHeight: 420 * scaling
- minimumWidth: 300 * scaling
+ popupHeight: 420
+ minimumWidth: 300
onSelected: function (key) {
Settings.data.ui.fontDefault = key
}
@@ -287,8 +101,8 @@ ColumnLayout {
currentKey: Settings.data.ui.fontFixed
placeholder: I18n.tr("settings.general.fonts.monospace.placeholder")
searchPlaceholder: I18n.tr("settings.general.fonts.monospace.search-placeholder")
- popupHeight: 320 * scaling
- minimumWidth: 300 * scaling
+ popupHeight: 320
+ minimumWidth: 300
onSelected: function (key) {
Settings.data.ui.fontFixed = key
}
@@ -301,7 +115,7 @@ ColumnLayout {
}
RowLayout {
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
NValueSlider {
@@ -316,8 +130,8 @@ ColumnLayout {
// Reset button container
Item {
- Layout.preferredWidth: 30 * scaling
- Layout.preferredHeight: 30 * scaling
+ Layout.preferredWidth: 30 * Style.uiScaleRatio
+ Layout.preferredHeight: 30 * Style.uiScaleRatio
NIconButton {
icon: "refresh"
@@ -338,7 +152,7 @@ ColumnLayout {
}
RowLayout {
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
NValueSlider {
@@ -353,8 +167,8 @@ ColumnLayout {
// Reset button container
Item {
- Layout.preferredWidth: 30 * scaling
- Layout.preferredHeight: 30 * scaling
+ Layout.preferredWidth: 30 * Style.uiScaleRatio
+ Layout.preferredHeight: 30 * Style.uiScaleRatio
NIconButton {
icon: "refresh"
@@ -369,4 +183,10 @@ ColumnLayout {
}
}
}
+
+ NDivider {
+ Layout.fillWidth: true
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
+ }
}
diff --git a/Modules/Settings/Tabs/HooksTab.qml b/Modules/Settings/Tabs/HooksTab.qml
index ae339d1e..0b664cd3 100644
--- a/Modules/Settings/Tabs/HooksTab.qml
+++ b/Modules/Settings/Tabs/HooksTab.qml
@@ -7,7 +7,7 @@ import qs.Widgets
ColumnLayout {
id: contentColumn
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
width: root.width
NHeader {
@@ -25,7 +25,7 @@ ColumnLayout {
ColumnLayout {
visible: Settings.data.hooks.enabled
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
NDivider {
@@ -78,7 +78,7 @@ ColumnLayout {
// Info section
ColumnLayout {
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
Layout.fillWidth: true
NLabel {
diff --git a/Modules/Settings/Tabs/LauncherTab.qml b/Modules/Settings/Tabs/LauncherTab.qml
index a01c13fb..b64f26b6 100644
--- a/Modules/Settings/Tabs/LauncherTab.qml
+++ b/Modules/Settings/Tabs/LauncherTab.qml
@@ -7,7 +7,7 @@ import qs.Widgets
ColumnLayout {
id: root
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
NHeader {
label: I18n.tr("settings.launcher.settings.section.label")
@@ -48,19 +48,19 @@ ColumnLayout {
}
ColumnLayout {
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
Layout.fillWidth: true
NText {
text: I18n.tr("settings.launcher.settings.background-opacity.label")
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: I18n.tr("settings.launcher.settings.background-opacity.description")
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
@@ -111,7 +111,7 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
}
diff --git a/Modules/Settings/Tabs/LocationTab.qml b/Modules/Settings/Tabs/LocationTab.qml
index 769735d2..b4ccfe4f 100644
--- a/Modules/Settings/Tabs/LocationTab.qml
+++ b/Modules/Settings/Tabs/LocationTab.qml
@@ -7,7 +7,7 @@ import qs.Widgets
ColumnLayout {
id: root
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
NHeader {
label: I18n.tr("settings.location.location.section.label")
@@ -17,7 +17,7 @@ ColumnLayout {
// Location section
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
NTextInput {
label: I18n.tr("settings.location.location.search.label")
@@ -37,7 +37,7 @@ ColumnLayout {
LocationService.resetWeather()
}
}
- Layout.maximumWidth: 420 * scaling
+ Layout.maximumWidth: 420
}
NText {
@@ -46,24 +46,24 @@ ColumnLayout {
"name": LocationService.stableName,
"coordinates": LocationService.displayCoordinates
})
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignRight
Layout.alignment: Qt.AlignBottom
- Layout.bottomMargin: Style.marginM * scaling
+ Layout.bottomMargin: Style.marginM
}
}
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
// Weather section
ColumnLayout {
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
Layout.fillWidth: true
NHeader {
@@ -81,13 +81,13 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
// Date & time section
ColumnLayout {
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
Layout.fillWidth: true
NHeader {
@@ -112,7 +112,7 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
}
diff --git a/Modules/Settings/Tabs/NetworkTab.qml b/Modules/Settings/Tabs/NetworkTab.qml
index b19a0819..90e3216f 100644
--- a/Modules/Settings/Tabs/NetworkTab.qml
+++ b/Modules/Settings/Tabs/NetworkTab.qml
@@ -9,7 +9,7 @@ import qs.Widgets
ColumnLayout {
id: root
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
NHeader {
description: I18n.tr("settings.network.section.description")
@@ -29,7 +29,7 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
}
diff --git a/Modules/Settings/Tabs/NotificationsTab.qml b/Modules/Settings/Tabs/NotificationsTab.qml
index 6673305d..beae8cdc 100644
--- a/Modules/Settings/Tabs/NotificationsTab.qml
+++ b/Modules/Settings/Tabs/NotificationsTab.qml
@@ -24,7 +24,7 @@ ColumnLayout {
// General Notification Settings
ColumnLayout {
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
NHeader {
@@ -77,13 +77,13 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
// Duration
ColumnLayout {
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
NHeader {
@@ -101,7 +101,7 @@ ColumnLayout {
// Low Urgency Duration
ColumnLayout {
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
Layout.fillWidth: true
NLabel {
@@ -122,7 +122,7 @@ ColumnLayout {
// Normal Urgency Duration
ColumnLayout {
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
Layout.fillWidth: true
NLabel {
@@ -143,7 +143,7 @@ ColumnLayout {
// Critical Urgency Duration
ColumnLayout {
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
Layout.fillWidth: true
NLabel {
@@ -164,8 +164,8 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
// Monitor Configuration
@@ -179,11 +179,15 @@ ColumnLayout {
delegate: NCheckbox {
Layout.fillWidth: true
label: modelData.name || I18n.tr("system.unknown")
- description: I18n.tr("system.monitor-description", {
- "model": modelData.model,
- "width": modelData.width,
- "height": modelData.height
- })
+ description: {
+ const compositorScale = CompositorService.getDisplayScale(modelData.name)
+ I18n.tr("system.monitor-description", {
+ "model": modelData.model,
+ "width": modelData.width * compositorScale,
+ "height": modelData.height * compositorScale,
+ "scale": compositorScale
+ })
+ }
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
@@ -198,7 +202,7 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
}
diff --git a/Modules/Settings/Tabs/OsdTab.qml b/Modules/Settings/Tabs/OsdTab.qml
index 93e4a733..edb16fd3 100644
--- a/Modules/Settings/Tabs/OsdTab.qml
+++ b/Modules/Settings/Tabs/OsdTab.qml
@@ -24,7 +24,7 @@ ColumnLayout {
// Display
ColumnLayout {
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
NComboBox {
@@ -62,13 +62,13 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
// General
ColumnLayout {
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
NHeader {
@@ -83,6 +83,13 @@ ColumnLayout {
onToggled: checked => Settings.data.osd.enabled = checked
}
+ NToggle {
+ label: I18n.tr("settings.osd.always-on-top.label")
+ description: I18n.tr("settings.osd.always-on-top.description")
+ checked: Settings.data.osd.alwaysOnTop
+ onToggled: checked => Settings.data.osd.alwaysOnTop = checked
+ }
+
NLabel {
label: I18n.tr("settings.osd.duration.auto-hide.label")
description: I18n.tr("settings.osd.duration.auto-hide.description")
@@ -101,13 +108,13 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
// Monitor Configuration
ColumnLayout {
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
NHeader {
@@ -120,11 +127,15 @@ ColumnLayout {
delegate: NCheckbox {
Layout.fillWidth: true
label: modelData.name || I18n.tr("system.unknown")
- description: I18n.tr("system.monitor-description", {
- "model": modelData.model,
- "width": modelData.width,
- "height": modelData.height
- })
+ description: {
+ const compositorScale = CompositorService.getDisplayScale(modelData.name)
+ I18n.tr("system.monitor-description", {
+ "model": modelData.model,
+ "width": modelData.width * compositorScale,
+ "height": modelData.height * compositorScale,
+ "scale": compositorScale
+ })
+ }
checked: (Settings.data.osd.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
diff --git a/Modules/Settings/Tabs/ScreenRecorderTab.qml b/Modules/Settings/Tabs/ScreenRecorderTab.qml
index 957579bf..f1bed075 100644
--- a/Modules/Settings/Tabs/ScreenRecorderTab.qml
+++ b/Modules/Settings/Tabs/ScreenRecorderTab.qml
@@ -9,7 +9,7 @@ import qs.Widgets
ColumnLayout {
id: root
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
NHeader {
label: I18n.tr("settings.screen-recorder.general.section.label")
@@ -18,7 +18,7 @@ ColumnLayout {
// Output Folder
ColumnLayout {
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
Layout.fillWidth: true
NTextInputButton {
@@ -43,13 +43,13 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
// Video Settings
ColumnLayout {
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
NHeader {
@@ -179,13 +179,13 @@ ColumnLayout {
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
// Audio Settings
ColumnLayout {
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
NHeader {
diff --git a/Modules/Settings/Tabs/UserInterfaceTab.qml b/Modules/Settings/Tabs/UserInterfaceTab.qml
new file mode 100644
index 00000000..13557c29
--- /dev/null
+++ b/Modules/Settings/Tabs/UserInterfaceTab.qml
@@ -0,0 +1,260 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import qs.Commons
+import qs.Services
+import qs.Widgets
+
+ColumnLayout {
+ id: root
+
+ // User Interface
+ ColumnLayout {
+ spacing: Style.marginL
+ Layout.fillWidth: true
+
+ NHeader {
+ label: I18n.tr("settings.user-interface.section.label")
+ description: I18n.tr("settings.user-interface.section.description")
+ }
+
+ NToggle {
+ label: I18n.tr("settings.user-interface.dim-desktop.label")
+ description: I18n.tr("settings.user-interface.dim-desktop.description")
+ checked: Settings.data.general.dimDesktop
+ onToggled: checked => Settings.data.general.dimDesktop = checked
+ }
+
+ NToggle {
+ label: I18n.tr("settings.user-interface.tooltips.label")
+ description: I18n.tr("settings.user-interface.tooltips.description")
+ checked: Settings.data.ui.tooltipsEnabled
+ onToggled: checked => Settings.data.ui.tooltipsEnabled = checked
+ }
+
+ NToggle {
+ label: I18n.tr("settings.user-interface.compact-lockscreen.label")
+ description: I18n.tr("settings.user-interface.compact-lockscreen.description")
+ checked: Settings.data.general.compactLockScreen
+ onToggled: checked => Settings.data.general.compactLockScreen = checked
+ }
+
+ NDivider {
+ Layout.fillWidth: true
+ Layout.topMargin: Style.marginL
+ Layout.bottomMargin: Style.marginL
+ }
+
+ ColumnLayout {
+ spacing: Style.marginXXS
+ Layout.fillWidth: true
+
+ NLabel {
+ label: I18n.tr("settings.user-interface.scaling.label")
+ description: I18n.tr("settings.user-interface.scaling.description")
+ }
+
+ RowLayout {
+ spacing: Style.marginL
+ Layout.fillWidth: true
+
+ NValueSlider {
+ Layout.fillWidth: true
+ from: 0.8
+ to: 1.2
+ stepSize: 0.05
+ value: Settings.data.general.scaleRatio
+ onMoved: value => Settings.data.general.scaleRatio = value
+ text: Math.floor(Settings.data.general.scaleRatio * 100) + "%"
+ }
+
+ // Reset button container
+ Item {
+ Layout.preferredWidth: 30 * Style.uiScaleRatio
+ Layout.preferredHeight: 30 * Style.uiScaleRatio
+
+ NIconButton {
+ icon: "refresh"
+ baseSize: Style.baseWidgetSize * 0.8
+ tooltipText: I18n.tr("settings.user-interface.scaling.reset-scaling")
+ onClicked: Settings.data.general.scaleRatio = 1.0
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+ }
+ }
+
+ ColumnLayout {
+ spacing: Style.marginXXS
+ Layout.fillWidth: true
+
+ NLabel {
+ label: I18n.tr("settings.user-interface.border-radius.label")
+ description: I18n.tr("settings.user-interface.border-radius.description")
+ }
+
+ RowLayout {
+ spacing: Style.marginL
+ Layout.fillWidth: true
+
+ NValueSlider {
+ Layout.fillWidth: true
+ from: 0
+ to: 1
+ stepSize: 0.01
+ value: Settings.data.general.radiusRatio
+ onMoved: value => Settings.data.general.radiusRatio = value
+ text: Math.floor(Settings.data.general.radiusRatio * 100) + "%"
+ }
+
+ // Reset button container
+ Item {
+ Layout.preferredWidth: 30 * Style.uiScaleRatio
+ Layout.preferredHeight: 30 * Style.uiScaleRatio
+
+ NIconButton {
+ icon: "refresh"
+ baseSize: Style.baseWidgetSize * 0.8
+ tooltipText: I18n.tr("settings.user-interface.border-radius.reset")
+ onClicked: Settings.data.general.radiusRatio = 1.0
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+ }
+ }
+
+ // Animation Speed
+ ColumnLayout {
+ spacing: Style.marginL
+ Layout.fillWidth: true
+
+ ColumnLayout {
+ spacing: Style.marginXXS
+ Layout.fillWidth: true
+ visible: !Settings.data.general.animationDisabled
+
+ NLabel {
+ label: I18n.tr("settings.user-interface.animation-speed.label")
+ description: I18n.tr("settings.user-interface.animation-speed.description")
+ }
+
+ RowLayout {
+ spacing: Style.marginL
+ Layout.fillWidth: true
+
+ NValueSlider {
+ Layout.fillWidth: true
+ from: 0.1
+ to: 2.0
+ stepSize: 0.01
+ value: Settings.data.general.animationSpeed
+ onMoved: value => Settings.data.general.animationSpeed = value
+ text: Math.round(Settings.data.general.animationSpeed * 100) + "%"
+ }
+
+ // Reset button container
+ Item {
+ Layout.preferredWidth: 30 * Style.uiScaleRatio
+ Layout.preferredHeight: 30 * Style.uiScaleRatio
+
+ NIconButton {
+ icon: "refresh"
+ baseSize: Style.baseWidgetSize * 0.8
+ tooltipText: I18n.tr("settings.user-interface.animation-speed.reset")
+ onClicked: Settings.data.general.animationSpeed = 1.0
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+ }
+ }
+
+ NToggle {
+ label: I18n.tr("settings.user-interface.animation-disable.label")
+ description: I18n.tr("settings.user-interface.animation-disable.description")
+ checked: Settings.data.general.animationDisabled
+ onToggled: checked => Settings.data.general.animationDisabled = checked
+ }
+ }
+ }
+
+ NDivider {
+ Layout.fillWidth: true
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
+ }
+
+ // Dock
+ ColumnLayout {
+ spacing: Style.marginL
+ Layout.fillWidth: true
+
+ NHeader {
+ label: I18n.tr("settings.general.screen-corners.section.label")
+ description: I18n.tr("settings.general.screen-corners.section.description")
+ }
+
+ NToggle {
+ label: I18n.tr("settings.general.screen-corners.show-corners.label")
+ description: I18n.tr("settings.general.screen-corners.show-corners.description")
+ checked: Settings.data.general.showScreenCorners
+ onToggled: checked => Settings.data.general.showScreenCorners = checked
+ }
+
+ NToggle {
+ label: I18n.tr("settings.general.screen-corners.solid-black.label")
+ description: I18n.tr("settings.general.screen-corners.solid-black.description")
+ checked: Settings.data.general.forceBlackScreenCorners
+ onToggled: checked => Settings.data.general.forceBlackScreenCorners = checked
+ }
+
+ ColumnLayout {
+ spacing: Style.marginXXS
+ Layout.fillWidth: true
+
+ NLabel {
+ label: I18n.tr("settings.general.screen-corners.radius.label")
+ description: I18n.tr("settings.general.screen-corners.radius.description")
+ }
+
+ RowLayout {
+ spacing: Style.marginL
+ Layout.fillWidth: true
+
+ NValueSlider {
+ Layout.fillWidth: true
+ from: 0
+ to: 2
+ stepSize: 0.01
+ value: Settings.data.general.screenRadiusRatio
+ onMoved: value => Settings.data.general.screenRadiusRatio = value
+ text: Math.floor(Settings.data.general.screenRadiusRatio * 100) + "%"
+ }
+
+ // Reset button container
+ Item {
+ Layout.preferredWidth: 30 * Style.uiScaleRatio
+ Layout.preferredHeight: 30 * Style.uiScaleRatio
+
+ NIconButton {
+ icon: "refresh"
+ baseSize: Style.baseWidgetSize * 0.8
+ tooltipText: I18n.tr("settings.general.screen-corners.radius.reset")
+ onClicked: Settings.data.general.screenRadiusRatio = 1.0
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+ }
+ }
+ }
+
+ NDivider {
+ Layout.fillWidth: true
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
+ }
+}
diff --git a/Modules/Settings/Tabs/WallpaperTab.qml b/Modules/Settings/Tabs/WallpaperTab.qml
index d8a2bc56..c93c60da 100644
--- a/Modules/Settings/Tabs/WallpaperTab.qml
+++ b/Modules/Settings/Tabs/WallpaperTab.qml
@@ -12,7 +12,7 @@ ColumnLayout {
property string specificFolderMonitorName: ""
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
NHeader {
label: I18n.tr("settings.wallpaper.settings.section.label")
@@ -24,12 +24,12 @@ ColumnLayout {
description: I18n.tr("settings.wallpaper.settings.enable-management.description")
checked: Settings.data.wallpaper.enabled
onToggled: checked => Settings.data.wallpaper.enabled = checked
- Layout.bottomMargin: Style.marginL * scaling
+ Layout.bottomMargin: Style.marginL
}
ColumnLayout {
visible: Settings.data.wallpaper.enabled
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
NTextInputButton {
@@ -56,29 +56,28 @@ ColumnLayout {
visible: Settings.data.wallpaper.enableMultiMonitorDirectories
Layout.fillWidth: true
- Layout.minimumWidth: 550 * scaling
- radius: Style.radiusM * scaling
+ radius: Style.radiusM
color: Color.mSurfaceVariant
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
- implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling
+ border.width: Math.max(1, Style.borderS)
+ implicitHeight: contentCol.implicitHeight + Style.marginL * 2
ColumnLayout {
id: contentCol
anchors.fill: parent
- anchors.margins: Style.marginXL * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginL
+ spacing: Style.marginM
Repeater {
model: Quickshell.screens || []
delegate: ColumnLayout {
Layout.fillWidth: true
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
NText {
text: (modelData.name || "Unknown")
color: Color.mPrimary
font.weight: Style.fontWeightBold
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
}
NTextInputButton {
@@ -101,13 +100,13 @@ ColumnLayout {
NDivider {
visible: Settings.data.wallpaper.enabled
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
ColumnLayout {
visible: Settings.data.wallpaper.enabled
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
NHeader {
@@ -184,13 +183,13 @@ ColumnLayout {
NDivider {
visible: Settings.data.wallpaper.enabled
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
ColumnLayout {
visible: Settings.data.wallpaper.enabled
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
NHeader {
@@ -225,7 +224,7 @@ ColumnLayout {
// Preset chips using Repeater
RowLayout {
id: presetRow
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
// Factorized presets data
property var intervalPresets: [5 * 60, 10 * 60, 15 * 60, 30 * 60, 45 * 60, 60 * 60, 90 * 60, 120 * 60]
@@ -272,8 +271,8 @@ ColumnLayout {
RowLayout {
id: customRow
visible: presetRow.customForcedVisible || !presetRow.isCurrentPreset
- spacing: Style.marginS * scaling
- Layout.topMargin: Style.marginS * scaling
+ spacing: Style.marginS
+ Layout.topMargin: Style.marginS
NTextInput {
label: I18n.tr("settings.wallpaper.automation.custom-interval.label")
@@ -313,8 +312,8 @@ ColumnLayout {
radius: height * 0.5
color: selected ? Color.mPrimary : Color.mSurfaceVariant
- implicitHeight: Math.max(Style.baseWidgetSize * 0.55 * scaling, 24 * scaling)
- implicitWidth: chipLabel.implicitWidth + Style.marginM * 1.5 * scaling
+ implicitHeight: Math.max(Style.baseWidgetSize * 0.55, 24)
+ implicitWidth: chipLabel.implicitWidth + Style.marginM * 1.5
border.width: 1
border.color: selected ? Color.transparent : Color.mOutline
@@ -328,15 +327,15 @@ ColumnLayout {
id: chipLabel
anchors.centerIn: parent
text: parent.label
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
color: parent.selected ? Color.mOnPrimary : Color.mOnSurface
}
}
NDivider {
Layout.fillWidth: true
- Layout.topMargin: Style.marginXL * scaling
- Layout.bottomMargin: Style.marginXL * scaling
+ Layout.topMargin: Style.marginXL
+ Layout.bottomMargin: Style.marginXL
}
NFilePicker {
diff --git a/Modules/Toast/SimpleToast.qml b/Modules/Toast/SimpleToast.qml
index 339e09c6..1575c4a4 100644
--- a/Modules/Toast/SimpleToast.qml
+++ b/Modules/Toast/SimpleToast.qml
@@ -16,8 +16,8 @@ Rectangle {
signal hidden
width: parent.width
- height: Math.round(contentLayout.implicitHeight + Style.marginL * 2 * scaling)
- radius: Style.radiusL * scaling
+ height: Math.round(contentLayout.implicitHeight + Style.marginL * 2)
+ radius: Style.radiusL
visible: false
opacity: 0
scale: initialScale
@@ -34,7 +34,7 @@ Rectangle {
return Color.mOutline
}
}
- border.width: Math.max(2, Style.borderM * scaling)
+ border.width: Math.max(2, Style.borderM)
Behavior on opacity {
NumberAnimation {
@@ -74,8 +74,8 @@ Rectangle {
RowLayout {
id: contentLayout
anchors.fill: parent
- anchors.margins: Style.marginL * scaling
- spacing: Style.marginL * scaling
+ anchors.margins: Style.marginL
+ spacing: Style.marginL
// Icon
NIcon {
@@ -100,13 +100,13 @@ Rectangle {
return Color.mOnSurface
}
}
- pointSize: Style.fontSizeXXL * 1.5 * scaling
+ pointSize: Style.fontSizeXXL * 1.5
Layout.alignment: Qt.AlignVCenter
}
// Label and description
ColumnLayout {
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
@@ -114,7 +114,7 @@ Rectangle {
Layout.fillWidth: true
text: root.message
color: Color.mOnSurface
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
wrapMode: Text.WordWrap
visible: text.length > 0
@@ -124,7 +124,7 @@ Rectangle {
Layout.fillWidth: true
text: root.description
color: Color.mOnSurface
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
wrapMode: Text.WordWrap
visible: text.length > 0
}
diff --git a/Modules/Toast/ToastOverlay.qml b/Modules/Toast/ToastOverlay.qml
index 1c8cabab..3051108d 100644
--- a/Modules/Toast/ToastOverlay.qml
+++ b/Modules/Toast/ToastOverlay.qml
@@ -11,8 +11,6 @@ Variants {
delegate: ToastScreen {
required property ShellScreen modelData
-
screen: modelData
- scaling: ScalingService.getScreenScale(modelData)
}
}
diff --git a/Modules/Toast/ToastScreen.qml b/Modules/Toast/ToastScreen.qml
index 0c732184..ee19a625 100644
--- a/Modules/Toast/ToastScreen.qml
+++ b/Modules/Toast/ToastScreen.qml
@@ -10,7 +10,6 @@ Item {
id: root
required property ShellScreen screen
- required property real scaling
// Local queue for this screen only (bounded to prevent memory issues)
property var messageQueue: []
@@ -20,15 +19,6 @@ Item {
// If true, immediately show new toasts
property bool replaceOnNew: true
- Connections {
- target: ScalingService
- function onScaleChanged(screenName, scale) {
- if (screenName === root.screen.name) {
- root.scaling = scale
- }
- }
- }
-
Connections {
target: ToastService
@@ -161,10 +151,10 @@ Item {
margins.top: {
if (!(anchors.top))
return 0
- var base = Style.marginM * scaling
+ var base = Style.marginM
if (Settings.data.bar.position === "top") {
- var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
- return (Style.barHeight * scaling) + base + floatExtraV
+ var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0
+ return (Style.barHeight) + base + floatExtraV
}
return base
}
@@ -172,10 +162,10 @@ Item {
margins.bottom: {
if (!(anchors.bottom))
return 0
- var base = Style.marginM * scaling
+ var base = Style.marginM
if (Settings.data.bar.position === "bottom") {
- var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
- return (Style.barHeight * scaling) + base + floatExtraV
+ var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0
+ return (Style.barHeight) + base + floatExtraV
}
return base
}
@@ -183,10 +173,10 @@ Item {
margins.left: {
if (!(anchors.left))
return 0
- var base = Style.marginM * scaling
+ var base = Style.marginM
if (Settings.data.bar.position === "left") {
- var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
- return (Style.barHeight * scaling) + base + floatExtraH
+ var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
+ return (Style.barHeight) + base + floatExtraH
}
return base
}
@@ -194,15 +184,15 @@ Item {
margins.right: {
if (!(anchors.right))
return 0
- var base = Style.marginM * scaling
+ var base = Style.marginM
if (Settings.data.bar.position === "right") {
- var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
- return (Style.barHeight * scaling) + base + floatExtraH
+ var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
+ return (Style.barHeight) + base + floatExtraH
}
return base
}
- implicitWidth: 420 * root.scaling
+ implicitWidth: 420
implicitHeight: toastItem.height
color: Color.transparent
diff --git a/Modules/Tooltip/Tooltip.qml b/Modules/Tooltip/Tooltip.qml
index 8e8e532a..43a6850c 100644
--- a/Modules/Tooltip/Tooltip.qml
+++ b/Modules/Tooltip/Tooltip.qml
@@ -8,7 +8,6 @@ import qs.Widgets
PopupWindow {
id: root
- property real scaling: 1.0
property int screenWidth: 0
property int screenHeight: 0
@@ -114,7 +113,6 @@ PopupWindow {
if (!screen || !target || !tipText || tipText === "")
return
- root.scaling = ScalingService.getScreenScaleByName(screen.name)
root.screenWidth = screen.width
root.screenHeight = screen.height
@@ -153,10 +151,10 @@ PopupWindow {
}
// Calculate tooltip dimensions
- const tipWidth = Math.min(tooltipText.implicitWidth + (padding * 2 * scaling), maxWidth * scaling)
+ const tipWidth = Math.min(tooltipText.implicitWidth + (padding * 2), maxWidth)
root.implicitWidth = tipWidth
- const tipHeight = tooltipText.implicitHeight + (padding * 2 * scaling)
+ const tipHeight = tooltipText.implicitHeight + (padding * 2)
root.implicitHeight = tipHeight
// Get target's global position
@@ -179,26 +177,26 @@ PopupWindow {
"dir": "bottom",
"space": spaceBottom,
"x": (targetWidth - tipWidth) / 2,
- "y": targetHeight + margin * scaling,
- "fits": spaceBottom >= tipHeight + margin * scaling
+ "y": targetHeight + margin,
+ "fits": spaceBottom >= tipHeight + margin
}, {
"dir": "top",
"space": spaceTop,
"x": (targetWidth - tipWidth) / 2,
- "y": -tipHeight - margin * scaling,
- "fits": spaceTop >= tipHeight + margin * scaling
+ "y": -tipHeight - margin,
+ "fits": spaceTop >= tipHeight + margin
}, {
"dir": "right",
"space": spaceRight,
- "x": targetWidth + margin * scaling,
+ "x": targetWidth + margin,
"y": (targetHeight - tipHeight) / 2,
- "fits": spaceRight >= tipWidth + margin * scaling
+ "fits": spaceRight >= tipWidth + margin
}, {
"dir": "left",
"space": spaceLeft,
- "x": -tipWidth - margin * scaling,
+ "x": -tipWidth - margin,
"y": (targetHeight - tipHeight) / 2,
- "fits": spaceLeft >= tipWidth + margin * scaling
+ "fits": spaceLeft >= tipWidth + margin
}]
// Find first position that fits
@@ -226,29 +224,29 @@ PopupWindow {
if (direction === "auto") {
const globalX = targetGlobal.x + newAnchorX
if (globalX < 0) {
- newAnchorX = -targetGlobal.x + margin * scaling
+ newAnchorX = -targetGlobal.x + margin
} else if (globalX + tipWidth > screenWidth) {
- newAnchorX = screenWidth - targetGlobal.x - tipWidth - margin * scaling
+ newAnchorX = screenWidth - targetGlobal.x - tipWidth - margin
}
}
} else {
// Manual direction positioning
switch (direction) {
case "left":
- newAnchorX = -tipWidth - margin * scaling
+ newAnchorX = -tipWidth - margin
newAnchorY = (targetHeight - tipHeight) / 2
break
case "right":
- newAnchorX = targetWidth + margin * scaling
+ newAnchorX = targetWidth + margin
newAnchorY = (targetHeight - tipHeight) / 2
break
case "top":
newAnchorX = (targetWidth - tipWidth) / 2
- newAnchorY = -tipHeight - margin * scaling
+ newAnchorY = -tipHeight - margin
break
case "bottom":
newAnchorX = (targetWidth - tipWidth) / 2
- newAnchorY = targetHeight + margin * scaling
+ newAnchorY = targetHeight + margin
break
}
}
@@ -370,8 +368,8 @@ PopupWindow {
anchors.fill: parent
color: Color.mSurface
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * root.scaling)
- radius: Style.radiusS * root.scaling
+ border.width: Math.max(1, Style.borderS)
+ radius: Style.radiusS
// Only show content when we have text
visible: root.text !== ""
@@ -379,14 +377,14 @@ PopupWindow {
NText {
id: tooltipText
anchors.centerIn: parent
- anchors.margins: root.padding * root.scaling
+ anchors.margins: root.padding
text: root.text
- pointSize: Style.fontSizeS * root.scaling
+ pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
- width: root.maxWidth * root.scaling - (root.padding * 2 * root.scaling)
+ width: root.maxWidth - (root.padding * 2)
}
}
}
diff --git a/Modules/Wallpaper/WallpaperPanel.qml b/Modules/Wallpaper/WallpaperPanel.qml
index 6ff2ed49..be3a526b 100644
--- a/Modules/Wallpaper/WallpaperPanel.qml
+++ b/Modules/Wallpaper/WallpaperPanel.qml
@@ -58,23 +58,23 @@ NPanel {
ColumnLayout {
anchors.fill: parent
- anchors.margins: Style.marginL * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginL
+ spacing: Style.marginM
// Header
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NIcon {
icon: "settings-wallpaper-selector"
- pointSize: Style.fontSizeXXL * scaling
+ pointSize: Style.fontSizeXXL
color: Color.mPrimary
}
NText {
text: I18n.tr("wallpaper.panel.title")
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.fillWidth: true
@@ -125,7 +125,7 @@ NPanel {
Layout.fillWidth: true
currentIndex: currentScreenIndex
onCurrentIndexChanged: currentScreenIndex = currentIndex
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
background: Rectangle {
color: Color.transparent
@@ -135,12 +135,12 @@ NPanel {
model: Quickshell.screens
delegate: TabButton {
text: modelData.name || `Screen ${index + 1}`
- width: implicitWidth + Style.marginS * 2 * scaling
+ width: implicitWidth + Style.marginS * 2
background: Rectangle {
color: screenTabBar.currentIndex === index ? Color.mSecondary : Color.transparent
- radius: Style.radiusS * scaling
- border.width: screenTabBar.currentIndex === index ? 0 : Math.max(1, Style.borderS * scaling)
+ radius: Style.radiusS
+ border.width: screenTabBar.currentIndex === index ? 0 : Math.max(1, Style.borderS)
border.color: Color.mOutline
Behavior on color {
@@ -152,7 +152,7 @@ NPanel {
contentItem: NText {
text: parent.text
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
font.weight: screenTabBar.currentIndex === index ? Style.fontWeightBold : Style.fontWeightRegular
family: Settings.data.ui.fontDefault
color: screenTabBar.currentIndex === index ? Color.mOnSecondary : Color.mOnSurfaceVariant
@@ -169,7 +169,7 @@ NPanel {
anchors.fill: parent
color: Color.mOnSurface
opacity: tabHover.hovered && screenTabBar.currentIndex !== index ? 0.08 : 0
- radius: Style.radiusS * scaling
+ radius: Style.radiusS
Behavior on opacity {
NumberAnimation {
@@ -200,12 +200,12 @@ NPanel {
// Filter input
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NText {
text: I18n.tr("wallpaper.panel.search")
color: Color.mOnSurface
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
Layout.preferredWidth: implicitWidth
}
@@ -306,7 +306,7 @@ NPanel {
ColumnLayout {
anchors.fill: parent
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
GridView {
id: wallpaperGridView
@@ -327,12 +327,12 @@ NPanel {
property int itemSize: cellWidth
cellWidth: Math.floor((width - leftMargin - rightMargin) / columns)
- cellHeight: Math.floor(itemSize * 0.7) + Style.marginXS * scaling + Style.fontSizeXS * scaling + Style.marginM * scaling
+ cellHeight: Math.floor(itemSize * 0.7) + Style.marginXS + Style.fontSizeXS + Style.marginM
- leftMargin: Style.marginS * scaling
- rightMargin: Style.marginS * scaling
- topMargin: Style.marginS * scaling
- bottomMargin: Style.marginS * scaling
+ leftMargin: Style.marginS
+ rightMargin: Style.marginS
+ topMargin: Style.marginS
+ bottomMargin: Style.marginS
onCurrentIndexChanged: {
// Synchronize scroll with current item position
@@ -377,7 +377,7 @@ NPanel {
property string filename: wallpaperPath.split('/').pop()
width: wallpaperGridView.itemSize
- spacing: Style.marginXS * scaling
+ spacing: Style.marginXS
Rectangle {
id: imageContainer
@@ -405,24 +405,24 @@ NPanel {
}
return Color.mSurface
}
- border.width: Math.max(1, Style.borderL * 1.5 * scaling)
+ border.width: Math.max(1, Style.borderL * 1.5)
}
Rectangle {
anchors.top: parent.top
anchors.right: parent.right
- anchors.margins: Style.marginS * scaling
- width: 28 * scaling
- height: 28 * scaling
+ anchors.margins: Style.marginS
+ width: 28
+ height: 28
radius: width / 2
color: Color.mSecondary
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
visible: isSelected
NIcon {
icon: "check"
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
font.weight: Style.fontWeightBold
color: Color.mOnSecondary
anchors.centerIn: parent
@@ -461,10 +461,10 @@ NPanel {
NText {
text: filename
color: (hoverHandler.hovered || isSelected || wallpaperGridView.currentIndex === index) ? Color.mOnSurface : Color.mOnSurfaceVariant
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
Layout.fillWidth: true
- Layout.leftMargin: Style.marginS * scaling
- Layout.rightMargin: Style.marginS * scaling
+ Layout.leftMargin: Style.marginS
+ Layout.rightMargin: Style.marginS
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
@@ -475,12 +475,12 @@ NPanel {
// Empty / scanning state
Rectangle {
color: Color.mSurface
- radius: Style.radiusM * scaling
+ radius: Style.radiusM
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
visible: (filteredWallpapers.length === 0 && !WallpaperService.scanning) || WallpaperService.scanning
Layout.fillWidth: true
- Layout.preferredHeight: 130 * scaling
+ Layout.preferredHeight: 130
ColumnLayout {
anchors.fill: parent
@@ -498,7 +498,7 @@ NPanel {
}
NIcon {
icon: "folder-open"
- pointSize: Style.fontSizeXXL * scaling
+ pointSize: Style.fontSizeXXL
color: Color.mOnSurface
Layout.alignment: Qt.AlignHCenter
}
diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml
index 21426aa6..6117fcdb 100644
--- a/Services/AppThemeService.qml
+++ b/Services/AppThemeService.qml
@@ -41,6 +41,12 @@ Singleton {
"path": "~/.config/qt6ct/colors/noctalia.conf"
}]
},
+ "kcolorscheme": {
+ "input": "kcolorscheme.colors",
+ "outputs": [{
+ "path": "~/.local/share/color-schemes/noctalia.colors"
+ }]
+ },
"fuzzel": {
"input": "fuzzel.conf",
"outputs": [{
@@ -55,7 +61,7 @@ Singleton {
}],
"postProcess": () => `${colorsApplyScript} pywalfox\n`
},
- "vesktop": {
+ "discord_vesktop": {
"input": "vesktop.css",
"outputs": [{
"path": "~/.config/vesktop/themes/noctalia.theme.css"
@@ -85,11 +91,13 @@ Singleton {
Logger.log("AppThemeService", "Service started")
}
+ // --------------------------------------------------------------------------------
function generate() {
if (Settings.data.colorSchemes.useWallpaperColors) {
generateFromWallpaper()
} else {
- generateFromPredefinedScheme()
+ // Re-apply the scheme, this is the best way to regenerate all templates too.
+ ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
}
}
diff --git a/Services/AudioService.qml b/Services/AudioService.qml
index f600f9d9..739fd221 100644
--- a/Services/AudioService.qml
+++ b/Services/AudioService.qml
@@ -54,7 +54,7 @@ Singleton {
function onVolumeChanged() {
var vol = (sink?.audio.volume ?? 0)
if (isNaN(vol)) {
- vol = 0
+ return
}
root._volume = vol
}
@@ -72,7 +72,7 @@ Singleton {
function onVolumeChanged() {
var vol = (source?.audio.volume ?? 0)
if (isNaN(vol)) {
- vol = 0
+ return
}
root._inputVolume = vol
}
@@ -111,6 +111,14 @@ Singleton {
}
}
+ function increaseInputVolume() {
+ setInputVolume(inputVolume + stepVolume)
+ }
+
+ function decreaseInputVolume() {
+ setInputVolume(inputVolume - stepVolume)
+ }
+
function setInputVolume(newVolume: real) {
if (source?.ready && source?.audio) {
// Clamp it accordingly
diff --git a/Services/BarService.qml b/Services/BarService.qml
index 9a28b550..0a839294 100644
--- a/Services/BarService.qml
+++ b/Services/BarService.qml
@@ -15,6 +15,8 @@ Singleton {
// Key format: "screenName|section|widgetId|index"
property var widgetInstances: ({})
+ signal activeWidgetsChanged
+
signal barReadyChanged(string screenName)
// Simple timer that run once when the widget structure has changed
@@ -68,6 +70,7 @@ Singleton {
timerCheckVisualizer.restart()
Logger.log("BarService", "Registered widget:", key)
+ root.activeWidgetsChanged()
}
// Unregister a widget instance
@@ -75,6 +78,7 @@ Singleton {
const key = [screenName, section, widgetId, index].join("|")
delete widgetInstances[key]
Logger.log("BarService", "Unregistered widget:", key)
+ root.activeWidgetsChanged()
}
// Lookup a specific widget instance (returns the actual QML instance)
diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml
index cddb544a..1bb0199c 100644
--- a/Services/BarWidgetRegistry.qml
+++ b/Services/BarWidgetRegistry.qml
@@ -41,9 +41,11 @@ Singleton {
"ActiveWindow": {
"allowUserSettings": true,
"showIcon": true,
- "autoHide": false,
- "scrollingMode": "hover",
- "width": 145
+ "hideMode": "hidden",
+ "scrollingMode"// "visible", "hidden", "transparent"
+ : "hover",
+ "width": 145,
+ "colorizeIcons": false
},
"Battery": {
"allowUserSettings": true,
@@ -83,8 +85,9 @@ Singleton {
},
"MediaMini": {
"allowUserSettings": true,
- "autoHide": false,
- "scrollingMode": "hover",
+ "hideMode": "hidden",
+ "scrollingMode"// "visible", "hidden", "transparent"
+ : "hover",
"showAlbumArt": false,
"showVisualizer": false,
"visualizerType": "linear"
@@ -114,7 +117,13 @@ Singleton {
"Taskbar": {
"allowUserSettings": true,
"onlySameOutput": true,
- "onlyActiveWorkspaces": true
+ "onlyActiveWorkspaces": true,
+ "colorizeIcons": false
+ },
+ "Tray": {
+ "allowUserSettings": true,
+ "blacklist": [],
+ "colorizeIcons": false
},
"Workspace": {
"allowUserSettings": true,
diff --git a/Services/ColorSchemeService.qml b/Services/ColorSchemeService.qml
index a211b4af..7e07f27a 100644
--- a/Services/ColorSchemeService.qml
+++ b/Services/ColorSchemeService.qml
@@ -150,7 +150,7 @@ Singleton {
Logger.log("ColorScheme", "Applying color scheme:", getBasename(path))
// Generate Matugen templates if any are enabled and setting allows it
- if (Settings.data.colorSchemes.generateTemplatesForPredefined && hasEnabledMatugenTemplates()) {
+ if (Settings.data.colorSchemes.generateTemplatesForPredefined && hasEnabledTemplates()) {
AppThemeService.generateFromPredefinedScheme(data)
}
} catch (e) {
@@ -159,9 +159,10 @@ Singleton {
}
}
- // Check if any Matugen templates are enabled
- function hasEnabledMatugenTemplates() {
- return Settings.data.templates.gtk || Settings.data.templates.qt || Settings.data.templates.kitty || Settings.data.templates.ghostty || Settings.data.templates.foot || Settings.data.templates.fuzzel || Settings.data.templates.vesktop || Settings.data.templates.pywalfox
+ // Check if any templates are enabled
+ function hasEnabledTemplates() {
+ return Settings.data.templates.gtk || Settings.data.templates.qt || Settings.data.templates.kitty || Settings.data.templates.ghostty || Settings.data.templates.foot || Settings.data.templates.fuzzel || Settings.data.templates.discord || Settings.data.templates.discord_vesktop || Settings.data.templates.discord_webcord
+ || Settings.data.templates.discord_armcord || Settings.data.templates.discord_equibop || Settings.data.templates.discord_lightcord || Settings.data.templates.discord_dorion || Settings.data.templates.pywalfox
}
// Writer to colors.json using a JsonAdapter for safety
diff --git a/Services/CompositorService.qml b/Services/CompositorService.qml
index 4d30421c..5640c9d4 100644
--- a/Services/CompositorService.qml
+++ b/Services/CompositorService.qml
@@ -2,6 +2,7 @@ pragma Singleton
import QtQuick
import Quickshell
+import Quickshell.Io
import qs.Commons
import qs.Services
@@ -11,12 +12,17 @@ Singleton {
// Compositor detection
property bool isHyprland: false
property bool isNiri: false
+ property bool isSway: false
// Generic workspace and window data
property ListModel workspaces: ListModel {}
property ListModel windows: ListModel {}
property int focusedWindowIndex: -1
+ // Display scale data
+ property var displayScales: ({})
+ property bool displayScalesLoaded: false
+
// Generic events
signal workspaceChanged
signal activeWindowChanged
@@ -25,20 +31,45 @@ Singleton {
// Backend service loader
property var backend: null
+ // Cache file path
+ property string displayCachePath: ""
+
Component.onCompleted: {
+ // Setup cache path (needs Settings to be available)
+ Qt.callLater(() => {
+ if (typeof Settings !== 'undefined' && Settings.cacheDir) {
+ displayCachePath = Settings.cacheDir + "display.json"
+ displayCacheFileView.path = displayCachePath
+ }
+ })
+
detectCompositor()
}
function detectCompositor() {
const hyprlandSignature = Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE")
- if (hyprlandSignature && hyprlandSignature.length > 0) {
- isHyprland = true
- isNiri = false
- backendLoader.sourceComponent = hyprlandComponent
- } else {
- // Default to Niri
+ const niriSocket = Quickshell.env("NIRI_SOCKET")
+ const swaySock = Quickshell.env("SWAYSOCK")
+ if (niriSocket && niriSocket.length > 0) {
isHyprland = false
isNiri = true
+ isSway = false
+ backendLoader.sourceComponent = niriComponent
+ } else if (hyprlandSignature && hyprlandSignature.length > 0) {
+ isHyprland = true
+ isNiri = false
+ isSway = false
+ backendLoader.sourceComponent = hyprlandComponent
+ } else if (swaySock && swaySock.length > 0) {
+ isHyprland = false
+ isNiri = false
+ isSway = true
+ backendLoader.sourceComponent = swayComponent
+ } else {
+ // Always fallback to Niri
+ isHyprland = false
+ isNiri = true
+ isSway = false
backendLoader.sourceComponent = niriComponent
}
}
@@ -54,6 +85,31 @@ Singleton {
}
}
+ // Cache FileView for display scales
+ FileView {
+ id: displayCacheFileView
+ printErrors: false
+ watchChanges: false
+
+ adapter: JsonAdapter {
+ id: displayCacheAdapter
+ property var displays: ({})
+ }
+
+ onLoaded: {
+ // Load cached display scales
+ displayScales = displayCacheAdapter.displays || {}
+ displayScalesLoaded = true
+ // Logger.log("CompositorService", "Loaded display scales from cache:", JSON.stringify(displayScales))
+ }
+
+ onLoadFailed: {
+ // Cache doesn't exist yet, will be created on first update
+ displayScalesLoaded = true
+ // Logger.log("CompositorService", "No display cache found, will create on first update")
+ }
+ }
+
// Hyprland backend component
Component {
id: hyprlandComponent
@@ -70,6 +126,14 @@ Singleton {
}
}
+ // Sway backend component
+ Component {
+ id: swayComponent
+ SwayService {
+ id: swayBackend
+ }
+ }
+
function setupBackendConnections() {
if (!backend)
return
@@ -128,6 +192,50 @@ Singleton {
windowListChanged()
}
+ // Update display scales from backend
+ function updateDisplayScales() {
+ if (!backend || !backend.queryDisplayScales) {
+ Logger.warn("CompositorService", "Backend does not support display scale queries")
+ return
+ }
+
+ backend.queryDisplayScales()
+ }
+
+ // Called by backend when display scales are ready
+ function onDisplayScalesUpdated(scales) {
+ displayScales = scales
+ saveDisplayScalesToCache()
+ displayScalesChanged()
+ Logger.log("CompositorService", "Display scales updated")
+ }
+
+ // Save display scales to cache
+ function saveDisplayScalesToCache() {
+ if (!displayCachePath) {
+ return
+ }
+
+ displayCacheAdapter.displays = displayScales
+ displayCacheFileView.writeAdapter()
+ }
+
+ // Public function to get scale for a specific display
+ function getDisplayScale(displayName) {
+ if (!displayName || !displayScales[displayName]) {
+ return 1.0
+ }
+ return displayScales[displayName].scale || 1.0
+ }
+
+ // Public function to get all display info for a specific display
+ function getDisplayInfo(displayName) {
+ if (!displayName || !displayScales[displayName]) {
+ return null
+ }
+ return displayScales[displayName]
+ }
+
// Get focused window
function getFocusedWindow() {
if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.count) {
@@ -139,15 +247,19 @@ Singleton {
// Get focused window title
function getFocusedWindowTitle() {
if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.count) {
- return windows.get(focusedWindowIndex).title || ""
+ var title = windows.get(focusedWindowIndex).title
+ if (title !== undefined) {
+ title = title.replace(/(\r\n|\n|\r)/g, "")
+ }
+ return title || ""
}
return ""
}
// Generic workspace switching
- function switchToWorkspace(workspaceId) {
+ function switchToWorkspace(workspace) {
if (backend && backend.switchToWorkspace) {
- backend.switchToWorkspace(workspaceId)
+ backend.switchToWorkspace(workspace)
} else {
Logger.warn("Compositor", "No backend available for workspace switching")
}
@@ -177,18 +289,18 @@ Singleton {
}
// Set focused window
- function focusWindow(windowId) {
+ function focusWindow(window) {
if (backend && backend.focusWindow) {
- backend.focusWindow(windowId)
+ backend.focusWindow(window)
} else {
Logger.warn("Compositor", "No backend available for window focus")
}
}
// Close window
- function closeWindow(windowId) {
+ function closeWindow(window) {
if (backend && backend.closeWindow) {
- backend.closeWindow(windowId)
+ backend.closeWindow(window)
} else {
Logger.warn("Compositor", "No backend available for window closing")
}
@@ -197,6 +309,7 @@ Singleton {
// Session management
function logout() {
if (backend && backend.logout) {
+ Logger.log("Compositor", "Logout requested")
backend.logout()
} else {
Logger.warn("Compositor", "No backend available for logout")
@@ -204,18 +317,22 @@ Singleton {
}
function shutdown() {
+ Logger.log("Compositor", "Shutdown requested")
Quickshell.execDetached(["shutdown", "-h", "now"])
}
function reboot() {
+ Logger.log("Compositor", "Reboot requested")
Quickshell.execDetached(["reboot"])
}
function suspend() {
+ Logger.log("Compositor", "Suspend requested")
Quickshell.execDetached(["systemctl", "suspend"])
}
function lockAndSuspend() {
+ Logger.log("Compositor", "Lock and suspend requested")
try {
if (PanelService && PanelService.lockScreen && !PanelService.lockScreen.active) {
PanelService.lockScreen.active = true
diff --git a/Services/ControlCenterWidgetRegistry.qml b/Services/ControlCenterWidgetRegistry.qml
new file mode 100644
index 00000000..ce2fcbf1
--- /dev/null
+++ b/Services/ControlCenterWidgetRegistry.qml
@@ -0,0 +1,75 @@
+pragma Singleton
+
+import QtQuick
+import Quickshell
+import qs.Commons
+import qs.Modules.ControlCenter.Widgets
+
+Singleton {
+ id: root
+
+ // Widget registry object mapping widget names to components
+ property var widgets: ({
+ "Bluetooth": bluetoothComponent,
+ "Notifications": notificationsComponent,
+ "KeepAwake": keepAwakeComponent,
+ "NightLight": nightLightComponent,
+ "PowerProfile": powerProfileComponent,
+ "ScreenRecorder": screenRecorderComponent,
+ "WiFi": wiFiComponent,
+ "WallpaperSelector": wallpaperSelectorComponent
+ })
+
+ property var widgetMetadata: ({})
+
+ // Component definitions - these are loaded once at startup
+ property Component bluetoothComponent: Component {
+ Bluetooth {}
+ }
+ property Component notificationsComponent: Component {
+ Notifications {}
+ }
+ property Component keepAwakeComponent: Component {
+ KeepAwake {}
+ }
+ property Component nightLightComponent: Component {
+ NightLight {}
+ }
+ property Component powerProfileComponent: Component {
+ PowerProfile {}
+ }
+ property Component screenRecorderComponent: Component {
+ ScreenRecorder {}
+ }
+ property Component wiFiComponent: Component {
+ WiFi {}
+ }
+ property Component wallpaperSelectorComponent: Component {
+ WallpaperSelector {}
+ }
+
+ function init() {
+ Logger.log("ControlCenterWidgetRegistry", "Service started")
+ }
+
+ // ------------------------------
+ // Helper function to get widget component by name
+ function getWidget(id) {
+ return widgets[id] || null
+ }
+
+ // Helper function to check if widget exists
+ function hasWidget(id) {
+ return id in widgets
+ }
+
+ // Get list of available widget id
+ function getAvailableWidgets() {
+ return Object.keys(widgets)
+ }
+
+ // Helper function to check if widget has user settings
+ function widgetHasUserSettings(id) {
+ return (widgetMetadata[id] !== undefined) && (widgetMetadata[id].allowUserSettings === true)
+ }
+}
diff --git a/Services/HyprlandService.qml b/Services/HyprlandService.qml
index c27beba1..0cafa965 100644
--- a/Services/HyprlandService.qml
+++ b/Services/HyprlandService.qml
@@ -1,6 +1,7 @@
import QtQuick
import Quickshell
import Quickshell.Hyprland
+import Quickshell.Io
import qs.Commons
Item {
@@ -15,6 +16,7 @@ Item {
signal workspaceChanged
signal activeWindowChanged
signal windowListChanged
+ signal displayScalesChanged
// Hyprland-specific properties
property bool initialized: false
@@ -40,6 +42,7 @@ Item {
Qt.callLater(() => {
safeUpdateWorkspaces()
safeUpdateWindows()
+ queryDisplayScales()
})
initialized = true
Logger.log("HyprlandService", "Initialized successfully")
@@ -48,6 +51,67 @@ Item {
}
}
+ // Query display scales
+ function queryDisplayScales() {
+ hyprlandMonitorsProcess.running = true
+ }
+
+ // Hyprland monitors process for display scale detection
+ // Hyprland monitors process for display scale detection
+ Process {
+ id: hyprlandMonitorsProcess
+ running: false
+ command: ["hyprctl", "monitors", "-j"]
+
+ property string accumulatedOutput: ""
+
+ stdout: SplitParser {
+ onRead: function (line) {
+ // Accumulate lines instead of parsing each one
+ hyprlandMonitorsProcess.accumulatedOutput += line
+ }
+ }
+
+ onExited: function (exitCode) {
+ if (exitCode !== 0 || !accumulatedOutput) {
+ Logger.error("HyprlandService", "Failed to query monitors, exit code:", exitCode)
+ accumulatedOutput = ""
+ return
+ }
+
+ try {
+ const monitorsData = JSON.parse(accumulatedOutput)
+ const scales = {}
+
+ for (const monitor of monitorsData) {
+ if (monitor.name) {
+ scales[monitor.name] = {
+ "name": monitor.name,
+ "scale": monitor.scale || 1.0,
+ "width": monitor.width || 0,
+ "height": monitor.height || 0,
+ "refresh_rate": monitor.refreshRate || 0,
+ "x": monitor.x || 0,
+ "y": monitor.y || 0,
+ "active_workspace": monitor.activeWorkspace ? monitor.activeWorkspace.id : -1,
+ "vrr": monitor.vrr || false,
+ "focused": monitor.focused || false
+ }
+ }
+ }
+
+ // Notify CompositorService (it will emit displayScalesChanged)
+ if (CompositorService && CompositorService.onDisplayScalesUpdated) {
+ CompositorService.onDisplayScalesUpdated(scales)
+ }
+ } catch (e) {
+ Logger.error("HyprlandService", "Failed to parse monitors:", e)
+ } finally {
+ // Clear accumulated output for next query
+ accumulatedOutput = ""
+ }
+ }
+ }
// Safe update wrapper
function safeUpdate() {
safeUpdateWindows()
@@ -188,7 +252,7 @@ Item {
"id": windowId,
"title": title,
"appId": appId,
- "workspaceId": wsId,
+ "workspaceId": wsId || -1,
"isFocused": focused,
"output": output
}
@@ -268,29 +332,34 @@ Item {
safeUpdateWorkspaces()
workspaceChanged()
updateTimer.restart()
+
+ const monitorsEvents = ["configreloaded", "monitoradded", "monitorremoved", "monitoraddedv2", "monitorremovedv2"]
+ if (monitorsEvents.includes(event.name)) {
+ Qt.callLater(queryDisplayScales)
+ }
}
}
// Public functions
- function switchToWorkspace(workspaceId) {
+ function switchToWorkspace(workspace) {
try {
- Hyprland.dispatch(`workspace ${workspaceId}`)
+ Hyprland.dispatch(`workspace ${workspace.idx}`)
} catch (e) {
Logger.error("HyprlandService", "Failed to switch workspace:", e)
}
}
- function focusWindow(windowId) {
+ function focusWindow(window) {
try {
- Hyprland.dispatch(`focuswindow address:0x${windowId.toString()}`)
+ Hyprland.dispatch(`focuswindow address:0x${window.id.toString()}`)
} catch (e) {
Logger.error("HyprlandService", "Failed to switch window:", e)
}
}
- function closeWindow(windowId) {
+ function closeWindow(window) {
try {
- Hyprland.dispatch(`killwindow address:0x${windowId}`)
+ Hyprland.dispatch(`killwindow address:0x${window.id}`)
} catch (e) {
Logger.error("HyprlandService", "Failed to close window:", e)
}
diff --git a/Services/IPCService.qml b/Services/IPCService.qml
index 074fe8e3..ddae8f78 100644
--- a/Services/IPCService.qml
+++ b/Services/IPCService.qml
@@ -43,6 +43,14 @@ Item {
function clear() {
NotificationService.clearHistory()
}
+
+ function dismissOldest() {
+ NotificationService.dismissOldestActive()
+ }
+
+ function dismissAll() {
+ NotificationService.dismissAllActive()
+ }
}
IpcHandler {
@@ -112,10 +120,14 @@ Item {
function muteOutput() {
AudioService.setOutputMuted(!AudioService.muted)
}
+ function increaseInput() {
+ AudioService.increaseInputVolume()
+ }
+ function decreaseInput() {
+ AudioService.decreaseInputVolume()
+ }
function muteInput() {
- if (AudioService.source?.ready && AudioService.source?.audio) {
- AudioService.source.audio.muted = !AudioService.source.audio.muted
- }
+ AudioService.setInputMuted(!AudioService.inputMuted)
}
}
@@ -124,6 +136,10 @@ Item {
function toggle() {
sessionMenuPanel.toggle()
}
+
+ function lockAndSuspend() {
+ CompositorService.lockAndSuspend()
+ }
}
IpcHandler {
@@ -188,4 +204,45 @@ Item {
}
}
}
+
+ IpcHandler {
+ target: "media"
+ function playPause() {
+ MediaService.playPause()
+ }
+
+ function play() {
+ MediaService.play()
+ }
+
+ function pause() {
+ MediaService.pause()
+ }
+
+ function next() {
+ MediaService.next()
+ }
+
+ function previous() {
+ MediaService.previous()
+ }
+
+ function seekRelative(offset: string) {
+ var offsetVal = parseFloat(position)
+ if (Number.isNaN(offsetVal)) {
+ Logger.warn("Media", "Argument to ipc call 'media seekRelative' must be a number")
+ return
+ }
+ MediaService.seekRelative(offsetVal)
+ }
+
+ function seekByRatio(position: string) {
+ var positionVal = parseFloat(position)
+ if (Number.isNaN(positionVal)) {
+ Logger.warn("Media", "Argument to ipc call 'media seekByRatio' must be a number")
+ return
+ }
+ MediaService.seekByRatio(positionVal)
+ }
+ }
}
diff --git a/Services/MatugenTemplates.qml b/Services/MatugenTemplates.qml
index b50450bc..c7da9c3c 100644
--- a/Services/MatugenTemplates.qml
+++ b/Services/MatugenTemplates.qml
@@ -69,65 +69,134 @@ Singleton {
})
}
+ // Applications configuration
+ readonly property var applications: [{
+ "name": "gtk",
+ "templates": [{
+ "version": "gtk3",
+ "output": "~/.config/gtk-3.0/gtk.css"
+ }, {
+ "version": "gtk4",
+ "output": "~/.config/gtk-4.0/gtk.css"
+ }],
+ "input": "gtk.css",
+ "postHook": "gsettings set org.gnome.desktop.interface color-scheme prefer-{mode}"
+ }, {
+ "name": "qt",
+ "templates": [{
+ "version": "qt5",
+ "output": "~/.config/qt5ct/colors/noctalia.conf"
+ }, {
+ "version": "qt6",
+ "output": "~/.config/qt6ct/colors/noctalia.conf"
+ }],
+ "input": "qtct.conf"
+ }, {
+ "name": "kcolorscheme",
+ "templates": [{
+ "version": "kcolorscheme",
+ "output": "~/.local/share/color-schemes/noctalia.colors"
+ }],
+ "input": "kcolorscheme.colors"
+ }, {
+ "name": "fuzzel",
+ "templates": [{
+ "version": "fuzzel",
+ "output": "~/.config/fuzzel/themes/noctalia"
+ }],
+ "input": "fuzzel.conf",
+ "postHook": AppThemeService.colorsApplyScript + " fuzzel"
+ }, {
+ "name": "pywalfox",
+ "templates": [{
+ "version": "pywalfox",
+ "output": "~/.cache/wal/colors.json"
+ }],
+ "input": "pywalfox.json",
+ "postHook": AppThemeService.colorsApplyScript + " pywalfox"
+ }, {
+ "name": "discord_vesktop",
+ "templates": [{
+ "version": "discord_vesktop",
+ "output": "~/.config/vesktop/themes/noctalia.theme.css"
+ }],
+ "input": "vesktop.css"
+ }, {
+ "name": "discord_webcord",
+ "templates": [{
+ "version": "discord_webcord",
+ "output": "~/.config/webcord/themes/noctalia.theme.css"
+ }],
+ "input": "vesktop.css"
+ }, {
+ "name": "discord_armcord",
+ "templates": [{
+ "version": "discord_armcord",
+ "output": "~/.config/armcord/themes/noctalia.theme.css"
+ }],
+ "input": "vesktop.css"
+ }, {
+ "name": "discord_equibop",
+ "templates": [{
+ "version": "discord_equibop",
+ "output": "~/.config/equibop/themes/noctalia.theme.css"
+ }],
+ "input": "vesktop.css"
+ }, {
+ "name": "discord_lightcord",
+ "templates": [{
+ "version": "discord_lightcord",
+ "output": "~/.config/lightcord/themes/noctalia.theme.css"
+ }],
+ "input": "vesktop.css"
+ }, {
+ "name": "discord_dorion",
+ "templates": [{
+ "version": "discord_dorion",
+ "output": "~/.config/dorion/themes/noctalia.theme.css"
+ }],
+ "input": "vesktop.css"
+ }]
+
// --------------------------------
function addApplicationTemplates(lines, mode) {
- var applications = [{
- "name": "gtk",
- "templates": [{
- "version": "gtk3",
- "output": "~/.config/gtk-3.0/gtk.css"
- }, {
- "version": "gtk4",
- "output": "~/.config/gtk-4.0/gtk.css"
- }],
- "input": "gtk.css",
- "postHook": "gsettings set org.gnome.desktop.interface color-scheme prefer-" + mode
- }, {
- "name": "qt",
- "templates": [{
- "version": "qt5",
- "output": "~/.config/qt5ct/colors/noctalia.conf"
- }, {
- "version": "qt6",
- "output": "~/.config/qt6ct/colors/noctalia.conf"
- }],
- "input": "qtct.conf"
- }, {
- "name": "fuzzel",
- "templates": [{
- "version": "fuzzel",
- "output": "~/.config/fuzzel/themes/noctalia"
- }],
- "input": "fuzzel.conf",
- "postHook": AppThemeService.colorsApplyScript + " fuzzel"
- }, {
- "name": "pywalfox",
- "templates": [{
- "version": "pywalfox",
- "output": "~/.cache/wal/colors.json"
- }],
- "input": "pywalfox.json",
- "postHook": AppThemeService.colorsApplyScript + " pywalfox"
- }, {
- "name": "vesktop",
- "templates": [{
- "version": "vesktop",
- "output": "~/.config/vesktop/themes/noctalia.theme.css"
- }],
- "input": "vesktop.css"
- }]
-
applications.forEach(function (app) {
- if (Settings.data.templates[app.name]) {
+ // Check if app has a condition and if it's met
+ var shouldInclude = true
+ if (app.condition !== undefined) {
+ shouldInclude = app.condition
+ }
+
+ if (Settings.data.templates[app.name] && shouldInclude) {
app.templates.forEach(function (template) {
lines.push("\n[templates." + template.version + "]")
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/MatugenTemplates/' + app.input + '"')
lines.push('output_path = "' + template.output + '"')
if (app.postHook) {
- lines.push('post_hook = "' + app.postHook + '"')
+ var postHook = app.postHook.replace("{mode}", mode)
+ lines.push('post_hook = "' + postHook + '"')
}
})
}
})
}
+
+ // Extract Discord clients from applications array
+ readonly property var discordClients: {
+ var clients = []
+ for (var i = 0; i < applications.length; i++) {
+ var app = applications[i]
+ if (app.name && app.name.startsWith("discord_")) {
+ var clientName = app.name.replace("discord_", "")
+ var themePath = app.templates[0].output
+ var configPath = themePath.replace("/themes/noctalia.theme.css", "")
+ clients.push({
+ "name": clientName,
+ "configPath": configPath,
+ "themePath": themePath
+ })
+ }
+ }
+ return clients
+ }
}
diff --git a/Services/MediaService.qml b/Services/MediaService.qml
index 1ae891ce..006285c9 100644
--- a/Services/MediaService.qml
+++ b/Services/MediaService.qml
@@ -14,7 +14,7 @@ Singleton {
property bool isSeeking: false
property int selectedPlayerIndex: 0
property bool isPlaying: currentPlayer ? (currentPlayer.playbackState === MprisPlaybackState.Playing || currentPlayer.isPlaying) : false
- property string trackTitle: currentPlayer ? (currentPlayer.trackTitle || "") : ""
+ property string trackTitle: currentPlayer ? (currentPlayer.trackTitle !== undefined ? currentPlayer.trackTitle.replace(/(\r\n|\n|\r)/g, "") : "") : ""
property string trackArtist: currentPlayer ? (currentPlayer.trackArtist || "") : ""
property string trackAlbum: currentPlayer ? (currentPlayer.trackAlbum || "") : ""
property string trackArtUrl: currentPlayer ? (currentPlayer.trackArtUrl || "") : ""
@@ -36,63 +36,126 @@ Singleton {
}
let allPlayers = Mpris.players.values
- let controllablePlayers = []
+ let finalPlayers = []
+ const genericBrowsers = ["firefox", "chromium", "chrome"]
- // Apply blacklist and controllable filter
- const blacklist = (Settings.data.audio && Settings.data.audio.mprisBlacklist) ? Settings.data.audio.mprisBlacklist : []
+ // Separate players into specific and generic lists
+ let specificPlayers = []
+ let genericPlayers = []
for (var i = 0; i < allPlayers.length; i++) {
- let player = allPlayers[i]
- if (!player)
- continue
- const identity = String(player.identity || "")
- const busName = String(player.busName || "")
- const desktop = String(player.desktopEntry || "")
- const idKey = identity.toLowerCase()
- const match = blacklist.find(b => {
- const s = String(b || "").toLowerCase()
- return s && (idKey.includes(s) || busName.toLowerCase().includes(s) || desktop.toLowerCase().includes(s))
- })
- if (match)
- continue
- if (player.canControl)
- controllablePlayers.push(player)
+ const identity = String(allPlayers[i].identity || "").toLowerCase()
+ if (genericBrowsers.some(b => identity.includes(b))) {
+ genericPlayers.push(allPlayers[i])
+ } else {
+ specificPlayers.push(allPlayers[i])
+ }
}
+ let matchedGenericIndices = {}
+
+ // For each specific player, try to find and pair it with a generic partner
+ for (var i = 0; i < specificPlayers.length; i++) {
+ let specificPlayer = specificPlayers[i]
+ let title1 = String(specificPlayer.trackTitle || "").trim()
+ let wasMatched = false
+
+ if (title1) {
+ for (var j = 0; j < genericPlayers.length; j++) {
+ if (matchedGenericIndices[j])
+ continue
+ let genericPlayer = genericPlayers[j]
+ let title2 = String(genericPlayer.trackTitle || "").trim()
+
+ if (title2 && (title1.includes(title2) || title2.includes(title1))) {
+ let dataPlayer = genericPlayer
+ let identityPlayer = specificPlayer
+
+ let scoreSpecific = (specificPlayer.trackArtUrl ? 1 : 0)
+ let scoreGeneric = (genericPlayer.trackArtUrl ? 1 : 0)
+ if (scoreSpecific > scoreGeneric) {
+ dataPlayer = specificPlayer
+ }
+
+ let virtualPlayer = {
+ "identity": identityPlayer.identity,
+ "desktopEntry": identityPlayer.desktopEntry,
+ "trackTitle": dataPlayer.trackTitle,
+ "trackArtist": dataPlayer.trackArtist,
+ "trackAlbum": dataPlayer.trackAlbum,
+ "trackArtUrl": dataPlayer.trackArtUrl,
+ "length": dataPlayer.length || 0,
+ "position": dataPlayer.position || 0,
+ "playbackState": dataPlayer.playbackState,
+ "isPlaying": dataPlayer.isPlaying || false,
+ "canPlay": dataPlayer.canPlay || false,
+ "canPause": dataPlayer.canPause || false,
+ "canGoNext": dataPlayer.canGoNext || false,
+ "canGoPrevious": dataPlayer.canGoPrevious || false,
+ "canSeek": dataPlayer.canSeek || false,
+ "canControl": dataPlayer.canControl || false,
+ "_stateSource": dataPlayer,
+ "_controlTarget": identityPlayer
+ }
+ finalPlayers.push(virtualPlayer)
+ matchedGenericIndices[j] = true
+ wasMatched = true
+ break
+ }
+ }
+ }
+ if (!wasMatched) {
+ finalPlayers.push(specificPlayer)
+ }
+ }
+
+ // Add any generic players that were not matched
+ for (var i = 0; i < genericPlayers.length; i++) {
+ if (!matchedGenericIndices[i]) {
+ finalPlayers.push(genericPlayers[i])
+ }
+ }
+
+ // Filter for controllable players
+ let controllablePlayers = []
+ for (var i = 0; i < finalPlayers.length; i++) {
+ let player = finalPlayers[i]
+ if (player && player.canControl) {
+ controllablePlayers.push(player)
+ }
+ }
return controllablePlayers
}
function findActivePlayer() {
let availablePlayers = getAvailablePlayers()
if (availablePlayers.length === 0) {
+ //Logger.log("Media", "No active player found")
return null
}
- // First, check if any player is currently playing
+ // Prioritize the actively playing player ---
for (var i = 0; i < availablePlayers.length; i++) {
- const p = availablePlayers[i]
- if (p.isPlaying && p.playbackState === MprisPlaybackState.Playing) {
+ if (availablePlayers[i] && availablePlayers[i].playbackState === MprisPlaybackState.Playing) {
+ Logger.log("Media", "Found actively playing player: " + availablePlayers[i].identity)
selectedPlayerIndex = i
- return p
+ return availablePlayers[i]
}
}
- // If no player is playing, use preferred player logic
+ // fallback if nothing is playing)
const preferred = (Settings.data.audio.preferredPlayer || "")
if (preferred !== "") {
for (var i = 0; i < availablePlayers.length; i++) {
const p = availablePlayers[i]
const identity = String(p.identity || "").toLowerCase()
- const busName = String(p.busName || "").toLowerCase()
- const desktop = String(p.desktopEntry || "").toLowerCase()
const pref = preferred.toLowerCase()
- if (identity.includes(pref) || busName.includes(pref) || desktop.includes(pref)) {
+ if (identity.includes(pref)) {
selectedPlayerIndex = i
return p
}
}
}
- // Fallback to selected index or first player
if (selectedPlayerIndex < availablePlayers.length) {
return availablePlayers[selectedPlayerIndex]
} else {
@@ -107,55 +170,64 @@ Singleton {
if (newPlayer !== currentPlayer) {
currentPlayer = newPlayer
currentPosition = currentPlayer ? currentPlayer.position : 0
+ Logger.log("Media", "Switching player")
}
}
function playPause() {
if (currentPlayer) {
- if (currentPlayer.isPlaying) {
- currentPlayer.pause()
+ let stateSource = currentPlayer._stateSource || currentPlayer
+ let controlTarget = currentPlayer._controlTarget || currentPlayer
+ if (stateSource.playbackState === MprisPlaybackState.Playing) {
+ controlTarget.pause()
} else {
- currentPlayer.play()
+ controlTarget.play()
}
}
}
function play() {
- if (currentPlayer && currentPlayer.canPlay) {
- currentPlayer.play()
+ let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null
+ if (target && target.canPlay) {
+ target.play()
}
}
function pause() {
- if (currentPlayer && currentPlayer.canPause) {
- currentPlayer.pause()
+ let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null
+ if (target && target.canPause) {
+ target.pause()
}
}
function next() {
- if (currentPlayer && currentPlayer.canGoNext) {
- currentPlayer.next()
+ let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null
+ if (target && target.canGoNext) {
+ target.next()
}
}
function previous() {
- if (currentPlayer && currentPlayer.canGoPrevious) {
- currentPlayer.previous()
+ let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null
+ if (target && target.canGoPrevious) {
+ target.previous()
}
}
function seek(position) {
- if (currentPlayer && currentPlayer.canSeek) {
- currentPlayer.position = position
+ let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null
+ if (target && target.canSeek) {
+ target.position = position
currentPosition = position
}
}
// Seek to position based on ratio (0.0 to 1.0)
function seekByRatio(ratio) {
- if (currentPlayer && currentPlayer.canSeek && currentPlayer.length > 0) {
- let seekPosition = ratio * currentPlayer.length
- currentPlayer.position = seekPosition
+ let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null
+ if (target && target.canSeek && target.length > 0) {
+ let seekPosition = ratio * target.length
+ target.position = seekPosition
currentPosition = seekPosition
}
}
@@ -197,15 +269,6 @@ Singleton {
}
}
- // Update current player when available players change
- Connections {
- target: Mpris.players
- function onValuesChanged() {
- updateCurrentPlayer()
- }
- }
-
- // Monitor playback state changes across all players to switch to playing ones
Timer {
id: playerStateMonitor
interval: 2000 // Check every 2 seconds
@@ -218,4 +281,13 @@ Singleton {
}
}
}
+
+ // Update current player when available players change
+ Connections {
+ target: Mpris.players
+ function onValuesChanged() {
+ Logger.log("Media", "Players changed")
+ updateCurrentPlayer()
+ }
+ }
}
diff --git a/Services/NiriService.qml b/Services/NiriService.qml
index 0c2d26f0..16226348 100644
--- a/Services/NiriService.qml
+++ b/Services/NiriService.qml
@@ -20,12 +20,14 @@ Item {
signal workspaceChanged
signal activeWindowChanged
signal windowListChanged
+ signal displayScalesChanged
// Initialization
function initialize() {
niriEventStream.running = true
updateWorkspaces()
updateWindows()
+ queryDisplayScales()
Logger.log("NiriService", "Initialized successfully")
}
@@ -39,6 +41,60 @@ Item {
niriWindowsProcess.running = true
}
+ // Query display scales
+ function queryDisplayScales() {
+ niriOutputsProcess.running = true
+ }
+
+ // Niri outputs process for display scale detection
+ Process {
+ id: niriOutputsProcess
+ running: false
+ command: ["niri", "msg", "--json", "outputs"]
+
+ stdout: SplitParser {
+ onRead: function (line) {
+ try {
+ const outputsData = JSON.parse(line)
+ const scales = {}
+
+ // Niri returns an object with display names as keys
+ for (const outputName in outputsData) {
+ const output = outputsData[outputName]
+ if (output && output.name) {
+ const logical = output.logical || {}
+ const currentModeIdx = output.current_mode || 0
+ const modes = output.modes || []
+ const currentMode = modes[currentModeIdx] || {}
+
+ scales[output.name] = {
+ "name": output.name,
+ "scale": logical.scale || 1.0,
+ "width": logical.width || 0,
+ "height": logical.height || 0,
+ "x": logical.x || 0,
+ "y": logical.y || 0,
+ "physical_width": (output.physical_size && output.physical_size[0]) || 0,
+ "physical_height": (output.physical_size && output.physical_size[1]) || 0,
+ "refresh_rate": currentMode.refresh_rate || 0,
+ "vrr_supported": output.vrr_supported || false,
+ "vrr_enabled": output.vrr_enabled || false,
+ "transform": logical.transform || "Normal"
+ }
+ }
+ }
+
+ // Notify CompositorService (it will emit displayScalesChanged)
+ if (CompositorService && CompositorService.onDisplayScalesUpdated) {
+ CompositorService.onDisplayScalesUpdated(scales)
+ }
+ } catch (e) {
+ Logger.error("NiriService", "Failed to parse outputs:", e, line)
+ }
+ }
+ }
+ }
+
// Niri workspace process
Process {
id: niriWorkspaceProcess
@@ -86,7 +142,7 @@ Item {
}
}
- // Niri windows process (for initial load)
+ // Niri windows process
Process {
id: niriWindowsProcess
running: false
@@ -131,6 +187,10 @@ Item {
handleWindowLayoutsChanged(event.WindowLayoutsChanged)
} else if (event.OverviewOpenedOrClosed) {
handleOverviewOpenedOrClosed(event.OverviewOpenedOrClosed)
+ } else if (event.OutputsChanged) {
+ queryDisplayScales()
+ } else if (event.ConfigLoaded) {
+ queryDisplayScales()
}
} catch (e) {
Logger.error("NiriService", "Error parsing event stream:", e, data)
@@ -332,25 +392,25 @@ Item {
}
// Public functions
- function switchToWorkspace(workspaceId) {
+ function switchToWorkspace(workspace) {
try {
- Quickshell.execDetached(["niri", "msg", "action", "focus-workspace", workspaceId.toString()])
+ Quickshell.execDetached(["niri", "msg", "action", "focus-workspace", workspace.idx.toString()])
} catch (e) {
Logger.error("NiriService", "Failed to switch workspace:", e)
}
}
- function focusWindow(windowId) {
+ function focusWindow(window) {
try {
- Quickshell.execDetached(["niri", "msg", "action", "focus-window", "--id", windowId.toString()])
+ Quickshell.execDetached(["niri", "msg", "action", "focus-window", "--id", window.id.toString()])
} catch (e) {
Logger.error("NiriService", "Failed to switch window:", e)
}
}
- function closeWindow(windowId) {
+ function closeWindow(window) {
try {
- Quickshell.execDetached(["niri", "msg", "action", "close-window", "--id", windowId.toString()])
+ Quickshell.execDetached(["niri", "msg", "action", "close-window", "--id", window.id.toString()])
} catch (e) {
Logger.error("NiriService", "Failed to close window:", e)
}
diff --git a/Services/NotificationService.qml b/Services/NotificationService.qml
index 2a4d23ce..249a5845 100644
--- a/Services/NotificationService.qml
+++ b/Services/NotificationService.qml
@@ -414,6 +414,13 @@ Singleton {
removeActive(id)
}
+ function dismissOldestActive() {
+ if (activeList.count > 0) {
+ const lastNotif = activeList.get(activeList.count - 1)
+ dismissActiveNotification(lastNotif.id)
+ }
+ }
+
function dismissAllActive() {
Object.values(activeMap).forEach(n => n.dismiss())
activeList.clear()
diff --git a/Services/PowerProfileService.qml b/Services/PowerProfileService.qml
index 98acfb71..9a43217e 100644
--- a/Services/PowerProfileService.qml
+++ b/Services/PowerProfileService.qml
@@ -71,6 +71,12 @@ Singleton {
setProfile(PowerProfile.Balanced)
}
+ function isDefault() {
+ if (!available)
+ return true
+ return (profile === PowerProfile.Balanced)
+ }
+
Connections {
target: powerProfiles
function onProfileChanged() {
diff --git a/Services/ProgramCheckerService.qml b/Services/ProgramCheckerService.qml
index a7d5f436..268274e3 100644
--- a/Services/ProgramCheckerService.qml
+++ b/Services/ProgramCheckerService.qml
@@ -16,13 +16,67 @@ Singleton {
property bool ghosttyAvailable: false
property bool footAvailable: false
property bool fuzzelAvailable: false
- property bool vesktopAvailable: false
property bool gpuScreenRecorderAvailable: false
property bool wlsunsetAvailable: false
+ // Discord client auto-detection
+ property var availableDiscordClients: []
+
// Signal emitted when all checks are complete
signal checksCompleted
+ // Function to detect Discord client by checking config directories
+ function detectDiscordClient() {
+ // Build list of client names from MatugenTemplates
+ var clientNames = []
+ for (var i = 0; i < MatugenTemplates.discordClients.length; i++) {
+ clientNames.push(MatugenTemplates.discordClients[i].name)
+ }
+
+ // Use a Process to check directory existence for all clients
+ discordDetector.command = ["sh", "-c", "available_clients=\"\"; " + "for client in " + clientNames.join(" ") + "; do " + " if [ -d \"$HOME/.config/$client\" ]; then " + " available_clients=\"$available_clients $client\"; " + " fi; " + "done; " + "echo \"$available_clients\""]
+ discordDetector.running = true
+ }
+
+ // Process to detect Discord client directories
+ Process {
+ id: discordDetector
+ running: false
+
+ onExited: function (exitCode) {
+ availableDiscordClients = []
+
+ if (exitCode === 0) {
+ var detectedClients = stdout.text.trim().split(/\s+/).filter(function (client) {
+ return client.length > 0
+ })
+
+ if (detectedClients.length > 0) {
+ // Build list of available clients
+ for (var i = 0; i < detectedClients.length; i++) {
+ var clientName = detectedClients[i]
+ for (var j = 0; j < MatugenTemplates.discordClients.length; j++) {
+ var client = MatugenTemplates.discordClients[j]
+ if (client.name === clientName) {
+ availableDiscordClients.push(client)
+ break
+ }
+ }
+ }
+
+ Logger.log("ProgramChecker", "Detected Discord clients:", detectedClients.join(", "))
+ }
+ }
+
+ if (availableDiscordClients.length === 0) {
+ Logger.log("ProgramChecker", "No Discord clients detected")
+ }
+ }
+
+ stdout: StdioCollector {}
+ stderr: StdioCollector {}
+ }
+
// Programs to check - maps property names to commands
readonly property var programsToCheck: ({
"matugenAvailable": ["which", "matugen"],
@@ -31,7 +85,6 @@ Singleton {
"ghosttyAvailable": ["which", "ghostty"],
"footAvailable": ["which", "foot"],
"fuzzelAvailable": ["which", "fuzzel"],
- "vesktopAvailable": ["which", "vesktop"],
"gpuScreenRecorderAvailable": ["sh", "-c", "command -v gpu-screen-recorder >/dev/null 2>&1 || (command -v flatpak >/dev/null 2>&1 && flatpak list --app | grep -q 'com.dec05eba.gpu_screen_recorder')"],
"wlsunsetAvailable": ["which", "wlsunset"]
})
@@ -59,6 +112,8 @@ Singleton {
// Check next program or emit completion signal
if (root.completedChecks >= root.totalChecks) {
+ // Run Discord client detection after all checks are complete
+ root.detectDiscordClient()
root.checksCompleted()
} else {
root.checkNextProgram()
@@ -113,6 +168,21 @@ Singleton {
checker.running = true
}
+ // Manual function to test Discord detection (for debugging)
+ function testDiscordDetection() {
+ Logger.log("ProgramChecker", "Testing Discord detection...")
+ Logger.log("ProgramChecker", "HOME:", Quickshell.env("HOME"))
+
+ // Test each client directory
+ for (var i = 0; i < MatugenTemplates.discordClients.length; i++) {
+ var client = MatugenTemplates.discordClients[i]
+ var configDir = client.configPath.replace("~", Quickshell.env("HOME"))
+ Logger.log("ProgramChecker", "Checking:", configDir)
+ }
+
+ detectDiscordClient()
+ }
+
// Initialize checks when service is created
Component.onCompleted: {
checkAllPrograms()
diff --git a/Services/ScalingService.qml b/Services/ScalingService.qml
deleted file mode 100644
index d85b9a3e..00000000
--- a/Services/ScalingService.qml
+++ /dev/null
@@ -1,131 +0,0 @@
-pragma Singleton
-
-import QtQuick
-import Quickshell
-import qs.Commons
-
-Singleton {
- id: root
-
- // Cache for current scales - updated via signals
- property var currentScales: ({})
-
- // Signal emitted when scale changes
- signal scaleChanged(string screenName, real scale)
-
- Component.onCompleted: {
- Logger.log("Scaling", "Service started")
- // Initialize cache from Settings once they are loaded on startup
- var monitors = Settings.data.ui.monitorsScaling || []
- for (var i = 0; i < monitors.length; i++) {
- if (monitors[i].name && monitors[i].scale !== undefined) {
- currentScales[monitors[i].name] = monitors[i].scale
- root.scaleChanged(monitors[i].name, monitors[i].scale)
- Logger.log("Scaling", "Caching scaling for", monitors[i].name, ":", monitors[i].scale)
- }
- }
- }
-
- // -------------------------------------------
- // Manual scaling via Settings
- function getScreenScale(aScreen) {
- try {
- if (aScreen !== undefined && aScreen.name !== undefined) {
- return getScreenScaleByName(aScreen.name)
- }
- } catch (e) {
-
- //Logger.warn(e)
- }
- return 1.0
- }
-
- // -------------------------------------------
- // Get scale from cache for better performance
- function getScreenScaleByName(aScreenName) {
- try {
- var scale = currentScales[aScreenName]
- if ((scale !== undefined) && (scale != null)) {
- return scale
- }
- } catch (e) {
-
- //Logger.warn(e)
- }
- return 1.0
- }
-
- // -------------------------------------------
- function setScreenScale(aScreen, scale) {
- try {
- if (aScreen !== undefined && aScreen.name !== undefined) {
- return setScreenScaleByName(aScreen.name, scale)
- }
- } catch (e) {
-
- //Logger.warn(e)
- }
- }
-
- // -------------------------------------------
- function setScreenScaleByName(aScreenName, scale) {
- try {
- // Check if scale actually changed
- var oldScale = currentScales[aScreenName] || 1.0
- if (oldScale === scale) {
- return
- // No change needed
- }
-
- // Update cache directly
- currentScales[aScreenName] = scale
-
- // Update Settings with immutable update for proper persistence
- var monitors = Settings.data.ui.monitorsScaling || []
- var found = false
-
- var newMonitors = monitors.map(function (monitor) {
- if (monitor.name === aScreenName) {
- found = true
- return {
- "name": aScreenName,
- "scale": scale
- }
- }
- return monitor
- })
-
- if (!found) {
- newMonitors.push({
- "name": aScreenName,
- "scale": scale
- })
- }
-
- // Use slice() to ensure Settings detects the change
- Settings.data.ui.monitorsScaling = newMonitors.slice()
-
- // Emit signal for components to react
- root.scaleChanged(aScreenName, scale)
-
- Logger.log("Scaling", "Scale changed for", aScreenName, "to", scale)
- } catch (e) {
- Logger.warn("Scaling", "Error setting scale:", e)
- }
- }
-
- // -------------------------------------------
- // Dynamic scaling based on resolution
- // Design reference resolution (for scale = 1.0)
- readonly property int designScreenWidth: 2560
- readonly property int designScreenHeight: 1440
- function dynamicScale(aScreen) {
- return 1.0
- // if (aScreen != null) {
- // var ratioW = aScreen.width / designScreenWidth
- // var ratioH = aScreen.height / designScreenHeight
- // return Math.min(ratioW, ratioH)
- // }
- // return 1.0
- }
-}
diff --git a/Services/ScreenRecorderService.qml b/Services/ScreenRecorderService.qml
index 7e57ce75..57fc578a 100644
--- a/Services/ScreenRecorderService.qml
+++ b/Services/ScreenRecorderService.qml
@@ -49,7 +49,7 @@ Singleton {
function launchRecorder() {
var filename = Time.getFormattedTimestamp() + ".mp4"
- var videoDir = settings.directory
+ var videoDir = Settings.preprocessPath(settings.directory)
if (videoDir && !videoDir.endsWith("/")) {
videoDir += "/"
}
diff --git a/Services/SwayService.qml b/Services/SwayService.qml
new file mode 100644
index 00000000..9e1ddd71
--- /dev/null
+++ b/Services/SwayService.qml
@@ -0,0 +1,302 @@
+import QtQuick
+import Quickshell
+import Quickshell.I3
+import Quickshell.Wayland
+import Quickshell.Io
+import qs.Commons
+
+Item {
+ id: root
+
+ // Properties that match the facade interface
+ property ListModel workspaces: ListModel {}
+ property var windows: []
+ property int focusedWindowIndex: -1
+
+ // Signals that match the facade interface
+ signal workspaceChanged
+ signal activeWindowChanged
+ signal windowListChanged
+ signal displayScalesChanged
+
+ // I3-specific properties
+ property bool initialized: false
+
+ // Debounce timer for updates
+ Timer {
+ id: updateTimer
+ interval: 50
+ repeat: false
+ onTriggered: safeUpdate()
+ }
+
+ // Initialization
+ function initialize() {
+ if (initialized)
+ return
+
+ try {
+ I3.refreshWorkspaces()
+ Qt.callLater(() => {
+ safeUpdateWorkspaces()
+ safeUpdateWindows()
+ queryDisplayScales()
+ })
+ initialized = true
+ Logger.log("SwayService", "Initialized successfully")
+ } catch (e) {
+ Logger.error("SwayService", "Failed to initialize:", e)
+ }
+ }
+
+ // Query display scales
+ function queryDisplayScales() {
+ swayOutputsProcess.running = true
+ }
+
+ // Sway outputs process for display scale detection
+ Process {
+ id: swayOutputsProcess
+ running: false
+ command: ["swaymsg", "-t", "get_outputs", "-r"]
+
+ property string accumulatedOutput: ""
+
+ stdout: SplitParser {
+ onRead: function (line) {
+ swayOutputsProcess.accumulatedOutput += line
+ }
+ }
+
+ onExited: function (exitCode) {
+ if (exitCode !== 0 || !accumulatedOutput) {
+ Logger.error("SwayService", "Failed to query outputs, exit code:", exitCode)
+ accumulatedOutput = ""
+ return
+ }
+
+ try {
+ const outputsData = JSON.parse(accumulatedOutput)
+ const scales = {}
+
+ for (const output of outputsData) {
+ if (output.name) {
+ scales[output.name] = {
+ "name": output.name,
+ "scale": output.scale || 1.0,
+ "width": output.current_mode ? output.current_mode.width : 0,
+ "height": output.current_mode ? output.current_mode.height : 0,
+ "refresh_rate": output.current_mode ? output.current_mode.refresh : 0,
+ "x": output.rect ? output.rect.x : 0,
+ "y": output.rect ? output.rect.y : 0,
+ "active": output.active || false,
+ "focused": output.focused || false,
+ "current_workspace": output.current_workspace || ""
+ }
+ }
+ }
+
+ // Notify CompositorService (it will emit displayScalesChanged)
+ if (CompositorService && CompositorService.onDisplayScalesUpdated) {
+ CompositorService.onDisplayScalesUpdated(scales)
+ }
+ } catch (e) {
+ Logger.error("SwayService", "Failed to parse outputs:", e)
+ } finally {
+ // Clear accumulated output for next query
+ accumulatedOutput = ""
+ }
+ }
+ }
+
+ // Safe update wrapper
+ function safeUpdate() {
+ safeUpdateWindows()
+ safeUpdateWorkspaces()
+ windowListChanged()
+ }
+
+ // Safe workspace update
+ function safeUpdateWorkspaces() {
+ try {
+ workspaces.clear()
+
+ if (!I3.workspaces || !I3.workspaces.values) {
+ return
+ }
+
+ const hlWorkspaces = I3.workspaces.values
+
+ for (var i = 0; i < hlWorkspaces.length; i++) {
+ const ws = hlWorkspaces[i]
+ if (!ws || ws.id < 1)
+ continue
+
+ const wsData = {
+ "id": i,
+ "idx": ws.num,
+ "name": ws.name || "",
+ "output": (ws.monitor && ws.monitor.name) ? ws.monitor.name : "",
+ "isActive": ws.active === true,
+ "isFocused": ws.focused === true,
+ "isUrgent": ws.urgent === true,
+ "isOccupied": true,
+ "handle": ws
+ }
+
+ workspaces.append(wsData)
+ }
+ } catch (e) {
+ Logger.error("SwayService", "Error updating workspaces:", e)
+ }
+ }
+
+ // Safe window update
+ function safeUpdateWindows() {
+ try {
+ const windowsList = []
+
+ if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values) {
+ windows = []
+ focusedWindowIndex = -1
+ return
+ }
+
+ const hlToplevels = ToplevelManager.toplevels.values
+ let newFocusedIndex = -1
+
+ for (var i = 0; i < hlToplevels.length; i++) {
+ const toplevel = hlToplevels[i]
+ if (!toplevel)
+ continue
+
+ const windowData = extractWindowData(toplevel)
+ if (windowData) {
+ windowsList.push(windowData)
+
+ if (windowData.isFocused) {
+ newFocusedIndex = windowsList.length - 1
+ }
+ }
+ }
+
+ windows = windowsList
+
+ if (newFocusedIndex !== focusedWindowIndex) {
+ focusedWindowIndex = newFocusedIndex
+ activeWindowChanged()
+ }
+ } catch (e) {
+ Logger.error("SwayService", "Error updating windows:", e)
+ }
+ }
+
+ // Extract window data safely from a toplevel
+ function extractWindowData(toplevel) {
+ if (!toplevel)
+ return null
+
+ try {
+ // Safely extract properties
+ const appId = extractAppId(toplevel)
+ const title = safeGetProperty(toplevel, "title", "")
+ const focused = toplevel.activated === true
+
+ return {
+ "title": title,
+ "appId": appId,
+ "isFocused": focused,
+ "handle": toplevel
+ }
+ } catch (e) {
+ return null
+ }
+ }
+
+ // Extract app ID from various possible sources
+ function extractAppId(toplevel) {
+ if (!toplevel)
+ return ""
+
+ return toplevel.appId
+ }
+
+ // Safe property getter
+ function safeGetProperty(obj, prop, defaultValue) {
+ try {
+ const value = obj[prop]
+ if (value !== undefined && value !== null) {
+ return String(value)
+ }
+ } catch (e) {
+
+ // Property access failed
+ }
+ return defaultValue
+ }
+
+ // Connections to I3
+ Connections {
+ target: I3.workspaces
+ enabled: initialized
+ function onValuesChanged() {
+ safeUpdateWorkspaces()
+ workspaceChanged()
+ }
+ }
+
+ Connections {
+ target: ToplevelManager
+ enabled: initialized
+ function onActiveToplevelChanged() {
+ updateTimer.restart()
+ }
+ }
+
+ Connections {
+ target: I3
+ enabled: initialized
+ function onRawEvent(event) {
+ safeUpdateWorkspaces()
+ workspaceChanged()
+ updateTimer.restart()
+
+ if (event.type === "output") {
+ Qt.callLater(queryDisplayScales)
+ }
+ }
+ }
+
+ // Public functions
+ function switchToWorkspace(workspace) {
+ try {
+ workspace.handle.activate()
+ } catch (e) {
+ Logger.error("SwayService", "Failed to switch workspace:", e)
+ }
+ }
+
+ function focusWindow(window) {
+ try {
+ window.handle.activate()
+ } catch (e) {
+ Logger.error("SwayService", "Failed to switch window:", e)
+ }
+ }
+
+ function closeWindow(window) {
+ try {
+ window.handle.close()
+ } catch (e) {
+ Logger.error("SwayService", "Failed to close window:", e)
+ }
+ }
+
+ function logout() {
+ try {
+ Quickshell.execDetached(["swaymsg", "exit"])
+ } catch (e) {
+ Logger.error("SwayService", "Failed to logout:", e)
+ }
+ }
+}
diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml
index d565fd2c..e1c864b5 100644
--- a/Services/UpdateService.qml
+++ b/Services/UpdateService.qml
@@ -8,7 +8,7 @@ Singleton {
id: root
// Public properties
- property string baseVersion: "2.16.1"
+ property string baseVersion: "2.18.0"
property bool isDevelopment: true
property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}`
diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml
index c60f2eba..2046f6cc 100644
--- a/Services/WallpaperService.qml
+++ b/Services/WallpaperService.qml
@@ -10,6 +10,7 @@ Singleton {
id: root
readonly property ListModel fillModeModel: ListModel {}
+ readonly property string defaultDirectory: Settings.preprocessPath(Settings.data.wallpaper.directory)
// All available wallpaper transitions
readonly property ListModel transitionsModel: ListModel {}
@@ -44,7 +45,7 @@ Singleton {
if (!Settings.data.wallpaper.enableMultiMonitorDirectories) {
// All monitors use the main directory
for (var i = 0; i < Quickshell.screens.length; i++) {
- root.wallpaperDirectoryChanged(Quickshell.screens[i].name, Settings.data.wallpaper.directory)
+ root.wallpaperDirectoryChanged(Quickshell.screens[i].name, root.defaultDirectory)
}
} else {
// Only monitors without custom directories are affected
@@ -52,7 +53,7 @@ Singleton {
var screenName = Quickshell.screens[i].name
var monitor = root.getMonitorConfig(screenName)
if (!monitor || !monitor.directory) {
- root.wallpaperDirectoryChanged(screenName, Settings.data.wallpaper.directory)
+ root.wallpaperDirectoryChanged(screenName, root.defaultDirectory)
}
}
}
@@ -177,16 +178,16 @@ Singleton {
// Get specific monitor directory
function getMonitorDirectory(screenName) {
if (!Settings.data.wallpaper.enableMultiMonitorDirectories) {
- return Settings.data.wallpaper.directory
+ return root.defaultDirectory
}
var monitor = getMonitorConfig(screenName)
if (monitor !== undefined && monitor.directory !== undefined) {
- return monitor.directory
+ return Settings.preprocessPath(monitor.directory)
}
// Fall back to the main/single directory
- return Settings.data.wallpaper.directory
+ return root.defaultDirectory
}
// -------------------------------------------------------------------
@@ -218,7 +219,7 @@ Singleton {
// Update Settings with new array to ensure proper persistence
Settings.data.wallpaper.monitors = newMonitors.slice()
- root.wallpaperDirectoryChanged(screenName, directory)
+ root.wallpaperDirectoryChanged(screenName, Settings.preprocessPath(directory))
}
// -------------------------------------------------------------------
@@ -274,7 +275,7 @@ Singleton {
found = true
return {
"name": screenName,
- "directory": monitor.directory || getMonitorDirectory(screenName),
+ "directory": Settings.preprocessPath(monitor.directory) || getMonitorDirectory(screenName),
"wallpaper": path
}
}
diff --git a/Shaders/frag/appicon_colorize.frag b/Shaders/frag/appicon_colorize.frag
new file mode 100644
index 00000000..ae108701
--- /dev/null
+++ b/Shaders/frag/appicon_colorize.frag
@@ -0,0 +1,29 @@
+#version 450
+layout(location = 0) in vec2 qt_TexCoord0;
+layout(location = 0) out vec4 fragColor;
+layout(binding = 1) uniform sampler2D source;
+layout(std140, binding = 0) uniform buf {
+ mat4 qt_Matrix;
+ float qt_Opacity;
+ vec4 targetColor;
+ float colorizeMode; // 0.0 = dock mode (grayscale), 1.0 = tray mode (intensity)
+} ubuf;
+
+void main() {
+ vec4 tex = texture(source, qt_TexCoord0);
+
+ float intensity;
+
+ if (ubuf.colorizeMode < 0.5) {
+ // Dock mode: Convert to grayscale using proper luminance weights
+ intensity = dot(tex.rgb, vec3(0.299, 0.587, 0.114));
+ } else {
+ // Tray mode: Use the maximum RGB channel value as intensity
+ intensity = max(max(tex.r, tex.g), tex.b);
+
+ // Normalize intensity to make all icons more uniform
+ intensity = smoothstep(0.1, 0.9, intensity);
+ }
+
+ fragColor = vec4(ubuf.targetColor.rgb * intensity, tex.a) * ubuf.qt_Opacity;
+}
diff --git a/Shaders/qsb/appicon_colorize.frag.qsb b/Shaders/qsb/appicon_colorize.frag.qsb
new file mode 100644
index 00000000..15618b86
Binary files /dev/null and b/Shaders/qsb/appicon_colorize.frag.qsb differ
diff --git a/Shaders/qsb/progress_border.frag.qsb b/Shaders/qsb/progress_border.frag.qsb
new file mode 100644
index 00000000..e6e8f315
Binary files /dev/null and b/Shaders/qsb/progress_border.frag.qsb differ
diff --git a/Widgets/NBox.qml b/Widgets/NBox.qml
index 4dad5c68..72ab90f0 100644
--- a/Widgets/NBox.qml
+++ b/Widgets/NBox.qml
@@ -12,7 +12,7 @@ Rectangle {
implicitHeight: childrenRect.height
color: Color.mSurfaceVariant
- radius: Style.radiusM * scaling
+ radius: Style.radiusM
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
}
diff --git a/Widgets/NBusyIndicator.qml b/Widgets/NBusyIndicator.qml
index 67f104ae..bf93abe8 100644
--- a/Widgets/NBusyIndicator.qml
+++ b/Widgets/NBusyIndicator.qml
@@ -7,8 +7,8 @@ Item {
property bool running: true
property color color: Color.mPrimary
- property int size: Style.baseWidgetSize * scaling
- property int strokeWidth: Style.borderL * scaling
+ property int size: Style.baseWidgetSize
+ property int strokeWidth: Style.borderL
property int duration: Style.animationSlow * 2
implicitWidth: size
diff --git a/Widgets/NButton.qml b/Widgets/NButton.qml
index 6ce1c75b..654de952 100644
--- a/Widgets/NButton.qml
+++ b/Widgets/NButton.qml
@@ -15,10 +15,11 @@ Rectangle {
property color textColor: Color.mOnPrimary
property color hoverColor: Color.mTertiary
property bool enabled: true
- property real fontSize: Style.fontSizeM * scaling
+ property real fontSize: Style.fontSizeM
property int fontWeight: Style.fontWeightBold
- property real iconSize: Style.fontSizeL * scaling
+ property real iconSize: Style.fontSizeL
property bool outlined: false
+ property int horizontalAlignment: Qt.AlignHCenter
// Signals
signal clicked
@@ -27,14 +28,13 @@ Rectangle {
// Internal properties
property bool hovered: false
- property bool pressed: false
// Dimensions
- implicitWidth: contentRow.implicitWidth + (Style.marginL * 2 * scaling)
- implicitHeight: Math.max(Style.baseWidgetSize * scaling, contentRow.implicitHeight + (Style.marginM * scaling))
+ implicitWidth: contentRow.implicitWidth + (Style.marginL * 2)
+ implicitHeight: Math.max(Style.baseWidgetSize, contentRow.implicitHeight + (Style.marginM))
// Appearance
- radius: Style.radiusS * scaling
+ radius: Style.radiusS
color: {
if (!enabled)
return outlined ? Color.transparent : Qt.lighter(Color.mSurfaceVariant, 1.2)
@@ -43,11 +43,11 @@ Rectangle {
return outlined ? Color.transparent : backgroundColor
}
- border.width: outlined ? Math.max(1, Style.borderS * scaling) : 0
+ border.width: outlined ? Math.max(1, Style.borderS) : 0
border.color: {
if (!enabled)
return Color.mOutline
- if (pressed || hovered)
+ if (hovered)
return backgroundColor
return outlined ? backgroundColor : Color.transparent
}
@@ -71,8 +71,11 @@ Rectangle {
// Content
RowLayout {
id: contentRow
- anchors.centerIn: parent
- spacing: Style.marginXS * scaling
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: root.horizontalAlignment === Qt.AlignLeft ? parent.left : undefined
+ anchors.horizontalCenter: root.horizontalAlignment === Qt.AlignHCenter ? parent.horizontalCenter : undefined
+ anchors.leftMargin: root.horizontalAlignment === Qt.AlignLeft ? Style.marginL : 0
+ spacing: Style.marginXS
// Icon (optional)
NIcon {
@@ -84,8 +87,8 @@ Rectangle {
if (!root.enabled)
return Color.mOnSurfaceVariant
if (root.outlined) {
- if (root.pressed || root.hovered)
- return root.backgroundColor
+ if (root.hovered)
+ return root.textColor
return root.backgroundColor
}
return root.textColor
diff --git a/Widgets/NCheckbox.qml b/Widgets/NCheckbox.qml
index 4ea76318..f27694e6 100644
--- a/Widgets/NCheckbox.qml
+++ b/Widgets/NCheckbox.qml
@@ -6,7 +6,7 @@ import qs.Commons
RowLayout {
id: root
- // Public API (mirrors NToggle but compact)
+ // Public API
property string label: ""
property string description: ""
property bool checked: false
@@ -35,12 +35,12 @@ RowLayout {
Rectangle {
id: box
- implicitWidth: Math.round(root.baseSize * scaling)
- implicitHeight: Math.round(root.baseSize * scaling)
- radius: Style.radiusXS * scaling
+ implicitWidth: Math.round(root.baseSize)
+ implicitHeight: Math.round(root.baseSize)
+ radius: Style.radiusXS
color: root.checked ? root.activeColor : Color.mSurface
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
Behavior on color {
ColorAnimation {
@@ -57,10 +57,10 @@ RowLayout {
NIcon {
visible: root.checked
anchors.centerIn: parent
- anchors.horizontalCenterOffset: -1 * scaling
+ anchors.horizontalCenterOffset: -1
icon: "check"
color: root.activeOnColor
- pointSize: Math.max(Style.fontSizeXS, root.baseSize * 0.5) * scaling
+ pointSize: Math.max(Style.fontSizeXS, root.baseSize * 0.5)
font.weight: Style.fontWeightBold
}
diff --git a/Widgets/NCircleStat.qml b/Widgets/NCircleStat.qml
index 53177d07..3279084e 100644
--- a/Widgets/NCircleStat.qml
+++ b/Widgets/NCircleStat.qml
@@ -17,12 +17,12 @@ Rectangle {
// outer width/height footprint of the component
property real contentScale: 1.0
- width: 68 * scaling
- height: 92 * scaling
+ width: 68
+ height: 92
color: flat ? Color.transparent : Color.mSurface
- radius: Style.radiusS * scaling
+ radius: Style.radiusS
border.color: flat ? Color.transparent : Color.mSurfaceVariant
- border.width: flat ? 0 : Math.max(1, Style.borderS * scaling)
+ border.width: flat ? 0 : Math.max(1, Style.borderS)
// Repaint gauge when the bound value changes
onValueChanged: gauge.requestPaint()
@@ -30,7 +30,7 @@ Rectangle {
ColumnLayout {
id: mainLayout
anchors.fill: parent
- anchors.margins: Style.marginS * scaling * contentScale
+ anchors.margins: Style.marginS * contentScale
spacing: 0
// Main gauge container
@@ -39,19 +39,19 @@ Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignCenter
- Layout.preferredWidth: 68 * scaling * contentScale
- Layout.preferredHeight: 68 * scaling * contentScale
+ Layout.preferredWidth: 68 * contentScale
+ Layout.preferredHeight: 68 * contentScale
Canvas {
id: gauge
anchors.fill: parent
- renderStrategy: Canvas.Cooperative
+ renderStrategy: Canvas.Immediate
onPaint: {
const ctx = getContext("2d")
const w = width, h = height
const cx = w / 2, cy = h / 2
- const r = Math.min(w, h) / 2 - 5 * scaling * contentScale
+ const r = Math.min(w, h) / 2 - 5 * contentScale
// Rotated 90° to the right: gap at the bottom
// Start at 150° and end at 390° (30°) → bottom opening
@@ -59,19 +59,33 @@ Rectangle {
const endBg = Math.PI * 13 / 6 // 390° (equivalent to 30°)
ctx.reset()
- ctx.lineWidth = 6 * scaling * contentScale
+ ctx.lineWidth = 6 * contentScale
- // Track uses surfaceVariant for stronger contrast
+ // Track uses surface for stronger contrast
ctx.strokeStyle = Color.mSurface
ctx.beginPath()
ctx.arc(cx, cy, r, start, endBg)
ctx.stroke()
- // Value arc
+ // Value arc with gradient starting at 25%
const ratio = Math.max(0, Math.min(1, root.value / 100))
const end = start + (endBg - start) * ratio
- ctx.strokeStyle = Color.mPrimary
+ // Calculate gradient start point (25% into the arc)
+ const gradientStartRatio = 0.25
+ const gradientStart = start + (endBg - start) * gradientStartRatio
+
+ // Create linear gradient
+ const startX = cx + r * Math.cos(gradientStart)
+ const startY = cy + r * Math.sin(gradientStart)
+ const endX = cx + r * Math.cos(endBg)
+ const endY = cy + r * Math.sin(endBg)
+
+ const gradient = ctx.createLinearGradient(startX, startY, endX, endY)
+ gradient.addColorStop(0, Color.mPrimary)
+ gradient.addColorStop(1, Color.mOnSurface)
+
+ ctx.strokeStyle = gradient
ctx.beginPath()
ctx.arc(cx, cy, r, start, end)
ctx.stroke()
@@ -82,34 +96,24 @@ Rectangle {
NText {
id: valueLabel
anchors.centerIn: parent
- anchors.verticalCenterOffset: -4 * scaling * contentScale
+ anchors.verticalCenterOffset: -4 * contentScale
text: `${root.value}${root.suffix}`
- pointSize: Style.fontSizeM * scaling * contentScale
+ pointSize: Style.fontSizeM * contentScale * 0.9
font.weight: Style.fontWeightBold
color: Color.mOnSurface
horizontalAlignment: Text.AlignHCenter
}
- // Tiny circular badge for the icon, positioned inside below the percentage
- Rectangle {
- id: iconBadge
- width: iconText.implicitWidth + Style.marginXXS * scaling
- height: width
- radius: width / 2
- color: Color.mPrimary
+ NIcon {
+ id: iconText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: valueLabel.bottom
- anchors.topMargin: 8 * scaling * contentScale
-
- NIcon {
- id: iconText
- anchors.centerIn: parent
- icon: root.icon
- color: Color.mOnPrimary
- pointSize: Style.fontSizeS * scaling
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
+ anchors.topMargin: 8 * contentScale
+ icon: root.icon
+ color: Color.mPrimary
+ pointSize: Style.fontSizeM
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
}
}
}
diff --git a/Widgets/NCollapsible.qml b/Widgets/NCollapsible.qml
index 6b78f4fd..3834220e 100644
--- a/Widgets/NCollapsible.qml
+++ b/Widgets/NCollapsible.qml
@@ -8,7 +8,7 @@ ColumnLayout {
property string description: ""
property bool expanded: false
property bool defaultExpanded: false
- property real contentSpacing: Style.marginM * scaling
+ property real contentSpacing: Style.marginM
signal toggled(bool expanded)
Layout.fillWidth: true
@@ -21,15 +21,15 @@ ColumnLayout {
Rectangle {
id: headerContainer
Layout.fillWidth: true
- Layout.preferredHeight: headerContent.implicitHeight + (Style.marginM * scaling * 2)
+ Layout.preferredHeight: headerContent.implicitHeight + (Style.marginM * 2)
// Material 3 style background
color: root.expanded ? Color.mSecondary : Color.mSurfaceVariant
- radius: Style.radiusL * scaling
+ radius: Style.radiusL
// Subtle border
border.color: root.expanded ? Color.mOnSecondary : Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
// Smooth color transitions
Behavior on color {
@@ -75,14 +75,14 @@ ColumnLayout {
RowLayout {
id: headerContent
anchors.fill: parent
- anchors.margins: Style.marginM * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginM
+ spacing: Style.marginM
// Expand/collapse icon with rotation animation
NIcon {
id: chevronIcon
icon: "chevron-right"
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
color: root.expanded ? Color.mOnSecondary : Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
@@ -105,11 +105,11 @@ ColumnLayout {
RowLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
NText {
text: root.label
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
font.weight: Style.fontWeightSemiBold
color: root.expanded ? Color.mOnSecondary : Color.mOnSurface
wrapMode: Text.WordWrap
@@ -123,7 +123,7 @@ ColumnLayout {
NText {
text: root.description
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
font.weight: Style.fontWeightRegular
color: root.expanded ? Color.mOnSecondary : Color.mOnSurfaceVariant
Layout.fillWidth: true
@@ -145,16 +145,16 @@ ColumnLayout {
Rectangle {
id: contentContainer
Layout.fillWidth: true
- Layout.topMargin: Style.marginS * scaling
+ Layout.topMargin: Style.marginS
visible: root.expanded
color: Color.mSurface
- radius: Style.radiusL * scaling
+ radius: Style.radiusL
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
// Dynamic height based on content
- Layout.preferredHeight: visible ? contentLayout.implicitHeight + (Style.marginL * scaling * 2) : 0
+ Layout.preferredHeight: visible ? contentLayout.implicitHeight + (Style.marginL * 2) : 0
// Smooth height animation
Behavior on Layout.preferredHeight {
@@ -168,7 +168,7 @@ ColumnLayout {
ColumnLayout {
id: contentLayout
anchors.fill: parent
- anchors.margins: Style.marginL * scaling
+ anchors.margins: Style.marginL
spacing: root.contentSpacing
}
diff --git a/Widgets/NColorPicker.qml b/Widgets/NColorPicker.qml
index 642d74a9..4aa9b46f 100644
--- a/Widgets/NColorPicker.qml
+++ b/Widgets/NColorPicker.qml
@@ -12,13 +12,13 @@ Rectangle {
signal colorSelected(color color)
- implicitWidth: 150 * scaling
- implicitHeight: Math.round(Style.baseWidgetSize * 1.1 * scaling)
+ implicitWidth: 150
+ implicitHeight: Math.round(Style.baseWidgetSize * 1.1)
- radius: Style.radiusM * scaling
+ radius: Style.radiusM
color: Color.mSurface
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
// Minimized Look
MouseArea {
@@ -41,19 +41,19 @@ Rectangle {
RowLayout {
anchors.fill: parent
anchors {
- leftMargin: Style.marginL * scaling
- rightMargin: Style.marginL * scaling
+ leftMargin: Style.marginL
+ rightMargin: Style.marginL
}
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
// Color preview circle
Rectangle {
- Layout.preferredWidth: root.height * 0.6 * scaling
- Layout.preferredHeight: root.height * 0.6 * scaling
+ Layout.preferredWidth: root.height * 0.6
+ Layout.preferredHeight: root.height * 0.6
radius: Layout.preferredWidth * 0.5
color: root.selectedColor
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
}
NText {
diff --git a/Widgets/NColorPickerDialog.qml b/Widgets/NColorPickerDialog.qml
index de6bb0e9..364dd8f9 100644
--- a/Widgets/NColorPickerDialog.qml
+++ b/Widgets/NColorPickerDialog.qml
@@ -14,12 +14,12 @@ Popup {
signal colorSelected(color color)
- width: 580 * scaling
+ width: 580
height: {
const h = scrollView.implicitHeight + padding * 2
- Math.min(h, screen?.height - Style.barHeight * scaling - Style.marginL * 2)
+ Math.min(h, screen?.height - Style.barHeight - Style.marginL * 2)
}
- padding: Style.marginXL * scaling
+ padding: Style.marginXL
// Center popup in parent
x: (parent.width - width) * 0.5
@@ -112,9 +112,9 @@ Popup {
background: Rectangle {
color: Color.mSurface
- radius: Style.radiusS * scaling
+ radius: Style.radiusS
border.color: Color.mPrimary
- border.width: Math.max(1, Style.borderM * scaling)
+ border.width: Math.max(1, Style.borderM)
}
contentItem: NScrollView {
@@ -126,24 +126,24 @@ Popup {
ColumnLayout {
width: scrollView.availableWidth
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
// Header
RowLayout {
Layout.fillWidth: true
RowLayout {
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
NIcon {
icon: "color-picker"
- pointSize: Style.fontSizeXXL * scaling
+ pointSize: Style.fontSizeXXL
color: Color.mPrimary
}
NText {
text: I18n.tr("widgets.color-picker.title")
- pointSize: Style.fontSizeXL * scaling
+ pointSize: Style.fontSizeXL
font.weight: Style.fontWeightBold
color: Color.mPrimary
}
@@ -162,11 +162,11 @@ Popup {
// Color preview section
Rectangle {
Layout.fillWidth: true
- Layout.preferredHeight: 80 * scaling
- radius: Style.radiusS * scaling
+ Layout.preferredHeight: 80
+ radius: Style.radiusS
color: root.selectedColor
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
ColumnLayout {
spacing: 0
@@ -179,7 +179,7 @@ Popup {
NText {
text: root.selectedColor.toString().toUpperCase()
family: Settings.data.ui.fontFixed
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
font.weight: Font.Bold
color: root.selectedColor.r + root.selectedColor.g + root.selectedColor.b > 1.5 ? "#000000" : "#FFFFFF"
Layout.alignment: Qt.AlignHCenter
@@ -188,7 +188,7 @@ Popup {
NText {
text: "RGB(" + Math.round(root.selectedColor.r * 255) + ", " + Math.round(root.selectedColor.g * 255) + ", " + Math.round(root.selectedColor.b * 255) + ")"
family: Settings.data.ui.fontFixed
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
color: root.selectedColor.r + root.selectedColor.g + root.selectedColor.b > 1.5 ? "#000000" : "#FFFFFF"
Layout.alignment: Qt.AlignHCenter
}
@@ -202,7 +202,7 @@ Popup {
// Hex input
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NLabel {
label: I18n.tr("widgets.color-picker.hex.label")
@@ -225,13 +225,13 @@ Popup {
// RGB sliders section
NBox {
Layout.fillWidth: true
- Layout.preferredHeight: slidersSection.implicitHeight + Style.marginL * scaling * 2
+ Layout.preferredHeight: slidersSection.implicitHeight + Style.marginL * 2
ColumnLayout {
id: slidersSection
anchors.fill: parent
- anchors.margins: Style.marginL * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginL
+ spacing: Style.marginM
NLabel {
label: I18n.tr("widgets.color-picker.rgb.label")
@@ -241,12 +241,12 @@ Popup {
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NText {
text: "R"
font.weight: Font.Bold
- Layout.preferredWidth: 20 * scaling
+ Layout.preferredWidth: 20
}
NValueSlider {
@@ -267,12 +267,12 @@ Popup {
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NText {
text: "G"
font.weight: Font.Bold
- Layout.preferredWidth: 20 * scaling
+ Layout.preferredWidth: 20
}
NValueSlider {
@@ -294,12 +294,12 @@ Popup {
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NText {
text: "B"
font.weight: Font.Bold
- Layout.preferredWidth: 20 * scaling
+ Layout.preferredWidth: 20
}
NValueSlider {
@@ -321,12 +321,12 @@ Popup {
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NText {
text: I18n.tr("widgets.color-picker.brightness")
font.weight: Font.Bold
- Layout.preferredWidth: 80 * scaling
+ Layout.preferredWidth: 80
}
NValueSlider {
@@ -361,13 +361,13 @@ Popup {
NBox {
Layout.fillWidth: true
- Layout.preferredHeight: themePalette.implicitHeight + Style.marginL * scaling * 2
+ Layout.preferredHeight: themePalette.implicitHeight + Style.marginL * 2
ColumnLayout {
id: themePalette
anchors.fill: parent
- anchors.margins: Style.marginL * scaling
- spacing: Style.marginS * scaling
+ anchors.margins: Style.marginL
+ spacing: Style.marginS
NLabel {
label: I18n.tr("widgets.color-picker.theme-colors.label")
@@ -376,7 +376,7 @@ Popup {
}
Flow {
- spacing: 6 * scaling
+ spacing: 6
Layout.fillWidth: true
flow: Flow.LeftToRight
@@ -384,12 +384,12 @@ Popup {
model: [Color.mPrimary, Color.mSecondary, Color.mTertiary, Color.mError, Color.mSurface, Color.mSurfaceVariant, Color.mOutline, "#FFFFFF", "#000000"]
Rectangle {
- width: 24 * scaling
- height: 24 * scaling
- radius: 4 * scaling
+ width: 24
+ height: 24
+ radius: 4
color: modelData
border.color: root.selectedColor === modelData ? Color.mPrimary : Color.mOutline
- border.width: root.selectedColor === modelData ? 2 * scaling : 1 * scaling
+ border.width: root.selectedColor === modelData ? 2 : 1
MouseArea {
anchors.fill: parent
@@ -409,13 +409,13 @@ Popup {
NBox {
Layout.fillWidth: true
- Layout.preferredHeight: genericPalette.implicitHeight + Style.marginL * scaling * 2
+ Layout.preferredHeight: genericPalette.implicitHeight + Style.marginL * 2
ColumnLayout {
id: genericPalette
anchors.fill: parent
- anchors.margins: Style.marginL * scaling
- spacing: Style.marginS * scaling
+ anchors.margins: Style.marginL
+ spacing: Style.marginS
NLabel {
label: I18n.tr("widgets.color-picker.palette.label")
@@ -426,19 +426,19 @@ Popup {
Flow {
Layout.fillWidth: true
Layout.fillHeight: true
- spacing: 6 * scaling
+ spacing: 6
flow: Flow.LeftToRight
Repeater {
model: ["#F44336", "#E91E63", "#9C27B0", "#673AB7", "#3F51B5", "#2196F3", "#03A9F4", "#00BCD4", "#009688", "#4CAF50", "#8BC34A", "#CDDC39", "#FFEB3B", "#FFC107", "#FF9800", "#FF5722", "#795548", "#9E9E9E", "#E74C3C", "#E67E22", "#F1C40F", "#2ECC71", "#1ABC9C", "#3498DB", "#2980B9", "#9B59B6", "#34495E", "#2C3E50", "#95A5A6", "#7F8C8D", "#FFFFFF", "#000000"]
Rectangle {
- width: 24 * scaling
- height: 24 * scaling
- radius: Style.radiusXXS * scaling
+ width: 24
+ height: 24
+ radius: Style.radiusXXS
color: modelData
border.color: root.selectedColor === modelData ? Color.mPrimary : Color.mOutline
- border.width: Math.max(1, root.selectedColor === modelData ? Style.borderM * scaling : Style.borderS * scaling)
+ border.width: Math.max(1, root.selectedColor === modelData ? Style.borderM : Style.borderS)
MouseArea {
anchors.fill: parent
@@ -458,9 +458,9 @@ Popup {
RowLayout {
Layout.fillWidth: true
- Layout.topMargin: 20 * scaling
- Layout.bottomMargin: 20 * scaling
- spacing: 10 * scaling
+ Layout.topMargin: 20
+ Layout.bottomMargin: 20
+ spacing: 10
Item {
Layout.fillWidth: true
diff --git a/Widgets/NComboBox.qml b/Widgets/NComboBox.qml
index a7a054b5..00f29bb1 100644
--- a/Widgets/NComboBox.qml
+++ b/Widgets/NComboBox.qml
@@ -8,8 +8,8 @@ import qs.Widgets
RowLayout {
id: root
- property real minimumWidth: 280 * scaling
- property real popupHeight: 180 * scaling
+ property real minimumWidth: 280 * Style.uiScaleRatio
+ property real popupHeight: 180 * Style.uiScaleRatio
property string label: ""
property string description: ""
@@ -17,11 +17,11 @@ RowLayout {
property string currentKey: ""
property string placeholder: ""
- readonly property real preferredHeight: Style.baseWidgetSize * 1.1 * scaling
+ readonly property real preferredHeight: Style.baseWidgetSize * 1.1 * Style.uiScaleRatio
signal selected(string key)
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
function itemCount() {
@@ -72,12 +72,12 @@ RowLayout {
}
background: Rectangle {
- implicitWidth: Style.baseWidgetSize * 3.75 * scaling
+ implicitWidth: Style.baseWidgetSize * 3.75
implicitHeight: preferredHeight
color: Color.mSurface
border.color: combo.activeFocus ? Color.mSecondary : Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
- radius: Style.radiusM * scaling
+ border.width: Math.max(1, Style.borderS)
+ radius: Style.radiusM
Behavior on border.color {
ColorAnimation {
@@ -87,9 +87,9 @@ RowLayout {
}
contentItem: NText {
- leftPadding: Style.marginL * scaling
- rightPadding: combo.indicator.width + Style.marginL * scaling
- pointSize: Style.fontSizeM * scaling
+ leftPadding: Style.marginL
+ rightPadding: combo.indicator.width + Style.marginL
+ pointSize: Style.fontSizeM
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
color: (combo.currentIndex >= 0 && combo.currentIndex < itemCount()) ? Color.mOnSurface : Color.mOnSurfaceVariant
@@ -97,17 +97,17 @@ RowLayout {
}
indicator: NIcon {
- x: combo.width - width - Style.marginM * scaling
+ x: combo.width - width - Style.marginM
y: combo.topPadding + (combo.availableHeight - height) / 2
icon: "caret-down"
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
}
popup: Popup {
y: combo.height
- implicitWidth: combo.width - Style.marginM * scaling
- implicitHeight: Math.min(root.popupHeight, contentItem.implicitHeight + Style.marginM * scaling * 2)
- padding: Style.marginM * scaling
+ implicitWidth: combo.width - Style.marginM
+ implicitHeight: Math.min(root.popupHeight, contentItem.implicitHeight + Style.marginM * 2)
+ padding: Style.marginM
onOpened: {
PanelService.willOpenPopup(root)
@@ -144,9 +144,9 @@ RowLayout {
}
background: Rectangle {
- width: combo.width - Style.marginM * scaling * 3
+ width: combo.width - Style.marginM * 3
color: highlighted ? Color.mTertiary : Color.transparent
- radius: Style.radiusS * scaling
+ radius: Style.radiusS
Behavior on color {
ColorAnimation {
duration: Style.animationFast
@@ -159,7 +159,7 @@ RowLayout {
var item = root.getItem(index)
return item && item.name ? item.name : ""
}
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
color: highlighted ? Color.mOnTertiary : Color.mOnSurface
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
@@ -175,8 +175,8 @@ RowLayout {
background: Rectangle {
color: Color.mSurfaceVariant
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
- radius: Style.radiusM * scaling
+ border.width: Math.max(1, Style.borderS)
+ radius: Style.radiusM
}
}
diff --git a/Widgets/NContextMenu.qml b/Widgets/NContextMenu.qml
index 126f2892..9f8e833e 100644
--- a/Widgets/NContextMenu.qml
+++ b/Widgets/NContextMenu.qml
@@ -8,13 +8,13 @@ Popup {
id: root
property alias model: listView.model
- property real itemHeight: 36 * scaling
- property real itemPadding: Style.marginM * scaling
+ property real itemHeight: 36
+ property real itemPadding: Style.marginM
signal triggered(string action)
- width: 180 * scaling
- padding: Style.marginS * scaling
+ width: 180
+ padding: Style.marginS
onOpened: PanelService.willOpenPopup(root)
onClosed: PanelService.willClosePopup(root)
@@ -22,14 +22,14 @@ Popup {
background: Rectangle {
color: Color.mSurfaceVariant
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
- radius: Style.radiusM * scaling
+ border.width: Math.max(1, Style.borderS)
+ radius: Style.radiusM
}
contentItem: NListView {
id: listView
implicitHeight: contentHeight
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
interactive: contentHeight > root.height
delegate: ItemDelegate {
@@ -45,7 +45,7 @@ Popup {
background: Rectangle {
color: menuItem.hovered && menuItem.enabled ? Color.mTertiary : Color.transparent
- radius: Style.radiusS * scaling
+ radius: Style.radiusS
Behavior on color {
ColorAnimation {
@@ -55,13 +55,13 @@ Popup {
}
contentItem: RowLayout {
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
// Optional icon
NIcon {
visible: modelData.icon !== undefined
icon: modelData.icon || ""
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
color: menuItem.hovered && menuItem.enabled ? Color.mOnTertiary : Color.mOnSurface
Layout.leftMargin: root.itemPadding
@@ -74,7 +74,7 @@ Popup {
NText {
text: modelData.label || modelData.text || ""
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
color: menuItem.hovered && menuItem.enabled ? Color.mOnTertiary : Color.mOnSurface
verticalAlignment: Text.AlignVCenter
Layout.fillWidth: true
diff --git a/Widgets/NDateTimeTokens.qml b/Widgets/NDateTimeTokens.qml
index 1ad1ec94..4f6c2b9e 100644
--- a/Widgets/NDateTimeTokens.qml
+++ b/Widgets/NDateTimeTokens.qml
@@ -7,8 +7,8 @@ Rectangle {
id: root
color: Color.mSurface
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
- radius: Style.radiusM * scaling
+ border.width: Math.max(1, Style.borderS)
+ radius: Style.radiusM
property date sampleDate: new Date() // Dec 25, 2023, 2:30:45.123 PM
@@ -18,8 +18,8 @@ Rectangle {
ColumnLayout {
id: column
anchors.fill: parent
- anchors.margins: Style.marginS * scaling
- spacing: Style.marginS * scaling
+ anchors.margins: Style.marginS
+ spacing: Style.marginS
NListView {
id: tokensList
@@ -174,8 +174,8 @@ Rectangle {
delegate: Rectangle {
id: tokenDelegate
width: tokensList.width
- height: layout.implicitHeight + Style.marginS * scaling
- radius: Style.radiusS * scaling
+ height: layout.implicitHeight + Style.marginS
+ radius: Style.radiusS
color: {
if (tokenMouseArea.containsMouse) {
return Qt.alpha(Color.mPrimary, 0.1)
@@ -216,16 +216,16 @@ Rectangle {
RowLayout {
id: layout
anchors.fill: parent
- anchors.margins: Style.marginXS * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginXS
+ spacing: Style.marginM
// Category badge
Rectangle {
Layout.alignment: Qt.AlignVCenter
- width: 70 * scaling
- height: 22 * scaling
+ width: 70
+ height: 22
color: getCategoryColor(modelData.category)[0]
- radius: Style.radiusS * scaling
+ radius: Style.radiusS
opacity: tokenMouseArea.containsMouse ? 0.9 : 1.0
Behavior on opacity {
@@ -238,7 +238,7 @@ Rectangle {
anchors.centerIn: parent
text: modelData.category
color: getCategoryColor(modelData.category)[1]
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
}
}
@@ -246,10 +246,10 @@ Rectangle {
Rectangle {
id: tokenButton
Layout.alignment: Qt.AlignVCenter // Added this line
- width: 100 * scaling
- height: 22 * scaling
+ width: 100
+ height: 22
color: tokenMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurface
- radius: Style.radiusS * scaling
+ radius: Style.radiusS
Behavior on color {
ColorAnimation {
@@ -261,7 +261,7 @@ Rectangle {
anchors.centerIn: parent
text: modelData.token
color: tokenMouseArea.containsMouse ? Color.mOnPrimary : Color.mSurface
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
font.weight: Style.fontWeightBold
Behavior on color {
@@ -278,7 +278,7 @@ Rectangle {
Layout.alignment: Qt.AlignVCenter // Added this line
text: modelData.description
color: tokenMouseArea.containsMouse ? Color.mOnSurface : Color.mOnSurfaceVariant
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
wrapMode: Text.WordWrap
Behavior on color {
@@ -291,12 +291,12 @@ Rectangle {
// Live example
Rectangle {
Layout.alignment: Qt.AlignVCenter // Added this line
- width: 90 * scaling
- height: 22 * scaling
+ width: 90
+ height: 22
color: tokenMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurfaceVariant
- radius: Style.radiusS * scaling
+ radius: Style.radiusS
border.color: tokenMouseArea.containsMouse ? Color.mPrimary : Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
Behavior on color {
ColorAnimation {
@@ -314,7 +314,7 @@ Rectangle {
anchors.centerIn: parent
text: Qt.locale().toString(root.sampleDate, modelData.token)
color: tokenMouseArea.containsMouse ? Color.mOnPrimary : Color.mSurfaceVariant
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
Behavior on color {
ColorAnimation {
diff --git a/Widgets/NDivider.qml b/Widgets/NDivider.qml
index a95df11d..98f4d1d4 100644
--- a/Widgets/NDivider.qml
+++ b/Widgets/NDivider.qml
@@ -6,7 +6,7 @@ import qs.Services
Rectangle {
width: parent.width
- height: Math.max(1, Style.borderS * scaling)
+ height: Math.max(1, Style.borderS)
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
diff --git a/Widgets/NFilePicker.qml b/Widgets/NFilePicker.qml
index 2eed6c9a..4fd7875e 100644
--- a/Widgets/NFilePicker.qml
+++ b/Widgets/NFilePicker.qml
@@ -13,13 +13,13 @@ Popup {
id: root
// Properties
- property string title: "File Picker"
+ property string title: I18n.tr("widget.file-picker.title")
property string initialPath: Quickshell.env("HOME") || "/home"
property string selectionMode: "files" // "files" or "folders"
property var nameFilters: ["*"]
property bool showDirs: true
property bool showHiddenFiles: false
- property real scaling: 1.0
+
property var selectedPaths: []
property string currentPath: initialPath
property bool shouldResetSelection: false
@@ -137,23 +137,23 @@ Popup {
}
}
- width: 900 * scaling
- height: 700 * scaling
+ width: 900
+ height: 700
modal: true
closePolicy: Popup.CloseOnEscape
anchors.centerIn: Overlay.overlay
background: Rectangle {
color: Color.mSurfaceVariant
- radius: Style.radiusL * scaling
+ radius: Style.radiusL
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
}
Rectangle {
id: filePickerPanel
anchors.fill: parent
- anchors.margins: Style.marginL * scaling
+ anchors.margins: Style.marginL
color: Color.transparent
property string filterText: ""
@@ -181,21 +181,21 @@ Popup {
ColumnLayout {
anchors.fill: parent
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Header
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NIcon {
icon: "filepicker-folder"
color: Color.mPrimary
- pointSize: Style.fontSizeXXL * scaling
+ pointSize: Style.fontSizeXXL
}
NText {
text: root.title
- pointSize: Style.fontSizeXL * scaling
+ pointSize: Style.fontSizeXL
font.weight: Style.fontWeightBold
color: Color.mPrimary
Layout.fillWidth: true
@@ -203,7 +203,7 @@ Popup {
// "Select Current" button only visible in folder selection mode
NButton {
- text: "Select Current"
+ text: I18n.tr("widgets.file-picker.select-current")
icon: "filepicker-folder-current"
visible: root.selectionMode === "folders"
onClicked: {
@@ -214,7 +214,7 @@ Popup {
NIconButton {
icon: "filepicker-refresh"
- tooltipText: "Refresh"
+ tooltipText: I18n.tr("tooltips.refresh")
onClicked: {
// Force a proper refresh by resetting the folder
const currentFolder = folderModel.folder
@@ -225,7 +225,7 @@ Popup {
}
NIconButton {
icon: "filepicker-close"
- tooltipText: "Close"
+ tooltipText: I18n.tr("tooltips.close")
onClicked: {
root.cancelled()
root.close()
@@ -240,23 +240,23 @@ Popup {
// Navigation toolbar
Rectangle {
Layout.fillWidth: true
- Layout.preferredHeight: 45 * scaling
+ Layout.preferredHeight: 45
color: Color.mSurfaceVariant
- radius: Style.radiusS * scaling
+ radius: Style.radiusS
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
RowLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
- anchors.leftMargin: Style.marginS * scaling
- anchors.rightMargin: Style.marginS * scaling
- spacing: Style.marginS * scaling
+ anchors.leftMargin: Style.marginS
+ anchors.rightMargin: Style.marginS
+ spacing: Style.marginS
NIconButton {
icon: "filepicker-arrow-up"
- tooltipText: "Up"
+ tooltipText: I18n.tr("tooltips.up")
baseSize: Style.baseWidgetSize * 0.8
enabled: folderModel.folder.toString() !== "file:///"
onClicked: {
@@ -268,7 +268,7 @@ Popup {
NIconButton {
icon: "filepicker-home"
- tooltipText: "Home"
+ tooltipText: I18n.tr("tooltips.home")
baseSize: Style.baseWidgetSize * 0.8
onClicked: {
const homePath = Quickshell.env("HOME") || "/home"
@@ -339,29 +339,29 @@ Popup {
// Search bar
Rectangle {
Layout.fillWidth: true
- Layout.preferredHeight: 45 * scaling
+ Layout.preferredHeight: 45
color: Color.mSurfaceVariant
- radius: Style.radiusS * scaling
+ radius: Style.radiusS
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
visible: filePickerPanel.showSearchBar
RowLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
- anchors.leftMargin: Style.marginS * scaling
- anchors.rightMargin: Style.marginS * scaling
- spacing: Style.marginS * scaling
+ anchors.leftMargin: Style.marginS
+ anchors.rightMargin: Style.marginS
+ spacing: Style.marginS
NIcon {
icon: "filepicker-search"
color: Color.mOnSurfaceVariant
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
}
NTextInput {
id: searchInput
- placeholderText: "Search files and folders..."
+ placeholderText: I18n.tr("widget.file-picker.search-placeholder")
Layout.fillWidth: true
text: filePickerPanel.searchText
onTextChanged: {
@@ -378,7 +378,7 @@ Popup {
}
NIconButton {
icon: "filepicker-x"
- tooltipText: "Clear"
+ tooltipText: I18n.tr("tooltips.clear")
baseSize: Style.baseWidgetSize * 0.6
visible: filePickerPanel.searchText.length > 0
onClicked: {
@@ -396,9 +396,9 @@ Popup {
Layout.fillWidth: true
Layout.fillHeight: true
color: Color.mSurface
- radius: Style.radiusM * scaling
+ radius: Style.radiusM
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
FolderListModel {
id: folderModel
@@ -447,9 +447,9 @@ Popup {
ScrollBar {
policy: ScrollBar.AsNeeded
contentItem: Rectangle {
- implicitWidth: 6 * scaling
+ implicitWidth: 6
implicitHeight: 100
- radius: Style.radiusM * scaling
+ radius: Style.radiusM
color: parent.pressed ? Qt.alpha(Color.mTertiary, 0.8) : parent.hovered ? Qt.alpha(Color.mTertiary, 0.8) : Qt.alpha(Color.mTertiary, 0.8)
opacity: parent.policy === ScrollBar.AlwaysOn || parent.active ? 1.0 : 0.0
Behavior on opacity {
@@ -464,11 +464,11 @@ Popup {
}
}
background: Rectangle {
- implicitWidth: 6 * scaling
+ implicitWidth: 6
implicitHeight: 100
color: Color.transparent
opacity: parent.policy === ScrollBar.AlwaysOn || parent.active ? 0.3 : 0.0
- radius: (Style.radiusM * scaling) / 2
+ radius: (Style.radiusM) / 2
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
@@ -482,22 +482,22 @@ Popup {
GridView {
id: gridView
anchors.fill: parent
- anchors.margins: Style.marginM * scaling
+ anchors.margins: Style.marginM
model: filteredModel
visible: filePickerPanel.viewMode
clip: true
reuseItems: true
- property int columns: Math.max(1, Math.floor(width / (120 * scaling)))
- property int itemSize: Math.floor((width - leftMargin - rightMargin - (columns * Style.marginS * scaling)) / columns)
+ property int columns: Math.max(1, Math.floor(width / (120)))
+ property int itemSize: Math.floor((width - leftMargin - rightMargin - (columns * Style.marginS)) / columns)
cellWidth: Math.floor((width - leftMargin - rightMargin) / columns)
- cellHeight: Math.floor(itemSize * 0.8) + Style.marginXS * scaling + Style.fontSizeS * scaling + Style.marginM * scaling
+ cellHeight: Math.floor(itemSize * 0.8) + Style.marginXS + Style.fontSizeS + Style.marginM
- leftMargin: Style.marginS * scaling
- rightMargin: Style.marginS * scaling
- topMargin: Style.marginS * scaling
- bottomMargin: Style.marginS * scaling
+ leftMargin: Style.marginS
+ rightMargin: Style.marginS
+ topMargin: Style.marginS
+ bottomMargin: Style.marginS
ScrollBar.vertical: scrollBarComponent.createObject(gridView, {
"parent": gridView,
@@ -511,7 +511,7 @@ Popup {
width: gridView.itemSize
height: gridView.cellHeight
color: Color.transparent
- radius: Style.radiusM * scaling
+ radius: Style.radiusM
property bool isSelected: filePickerPanel.currentSelection.includes(model.filePath)
@@ -520,7 +520,7 @@ Popup {
color: Color.transparent
radius: parent.radius
border.color: isSelected ? Color.mSecondary : Color.mSurface
- border.width: Math.max(1, Style.borderL * scaling)
+ border.width: Math.max(1, Style.borderL)
Behavior on color {
ColorAnimation {
duration: Style.animationFast
@@ -533,7 +533,7 @@ Popup {
color: (mouseArea.containsMouse && !isSelected) ? Color.mTertiary : Color.transparent
radius: parent.radius
border.color: (mouseArea.containsMouse && !isSelected) ? Color.mTertiary : Color.transparent
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
Behavior on color {
ColorAnimation {
duration: Style.animationFast
@@ -548,8 +548,8 @@ Popup {
ColumnLayout {
anchors.fill: parent
- anchors.margins: Style.marginS * scaling
- spacing: Style.marginXS * scaling
+ anchors.margins: Style.marginS
+ spacing: Style.marginXS
Rectangle {
id: iconContainer
@@ -567,15 +567,15 @@ Popup {
Image {
id: thumbnail
anchors.fill: parent
- anchors.margins: Style.marginXS * scaling
+ anchors.margins: Style.marginXS
source: iconContainer.isImage ? "file://" + model.filePath : ""
fillMode: Image.PreserveAspectFit
visible: iconContainer.isImage && status === Image.Ready
smooth: false
cache: true
asynchronous: true
- sourceSize.width: 120 * scaling
- sourceSize.height: 120 * scaling
+ sourceSize.width: 120
+ sourceSize.height: 120
onStatusChanged: {
if (status === Image.Error)
visible = false
@@ -584,11 +584,11 @@ Popup {
Rectangle {
anchors.fill: parent
color: Color.mSurfaceVariant
- radius: Style.radiusS * scaling
+ radius: Style.radiusS
visible: thumbnail.status === Image.Loading
NIcon {
icon: "filepicker-photo"
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
color: Color.mOnSurfaceVariant
anchors.centerIn: parent
}
@@ -597,7 +597,7 @@ Popup {
NIcon {
icon: model.fileIsDir ? "filepicker-folder" : root.getFileIcon(model.fileName)
- pointSize: Style.fontSizeXXL * 2 * scaling
+ pointSize: Style.fontSizeXXL * 2
color: {
if (isSelected)
return Color.mSecondary
@@ -613,17 +613,17 @@ Popup {
Rectangle {
anchors.top: parent.top
anchors.right: parent.right
- anchors.margins: Style.marginS * scaling
- width: 24 * scaling
- height: 24 * scaling
+ anchors.margins: Style.marginS
+ width: 24
+ height: 24
radius: width / 2
color: Color.mSecondary
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
visible: isSelected
NIcon {
icon: "filepicker-check"
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
font.weight: Style.fontWeightBold
color: Color.mOnSecondary
anchors.centerIn: parent
@@ -641,7 +641,7 @@ Popup {
else
return Color.mOnSurfaceVariant
}
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
font.weight: isSelected ? Style.fontWeightBold : Style.fontWeightRegular
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
@@ -697,14 +697,14 @@ Popup {
NListView {
id: listView
anchors.fill: parent
- anchors.margins: Style.marginS * scaling
+ anchors.margins: Style.marginS
model: filteredModel
visible: !filePickerPanel.viewMode
delegate: Rectangle {
id: listItem
width: listView.width
- height: 40 * scaling
+ height: 40
color: {
if (filePickerPanel.currentSelection.includes(model.filePath))
return Color.mSecondary
@@ -712,7 +712,7 @@ Popup {
return Color.mTertiary
return Color.transparent
}
- radius: Style.radiusS * scaling
+ radius: Style.radiusS
Behavior on color {
ColorAnimation {
duration: Style.animationFast
@@ -721,20 +721,20 @@ Popup {
RowLayout {
anchors.fill: parent
- anchors.leftMargin: Style.marginM * scaling
- anchors.rightMargin: Style.marginM * scaling
- spacing: Style.marginM * scaling
+ anchors.leftMargin: Style.marginM
+ anchors.rightMargin: Style.marginM
+ spacing: Style.marginM
NIcon {
icon: model.fileIsDir ? "filepicker-folder" : root.getFileIcon(model.fileName)
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
color: model.fileIsDir ? (filePickerPanel.currentSelection.includes(model.filePath) ? Color.mOnSecondary : Color.mPrimary) : Color.mOnSurfaceVariant
}
NText {
text: model.fileName
color: filePickerPanel.currentSelection.includes(model.filePath) ? Color.mOnSecondary : Color.mOnSurface
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
font.weight: filePickerPanel.currentSelection.includes(model.filePath) ? Style.fontWeightBold : Style.fontWeightRegular
Layout.fillWidth: true
elide: Text.ElideRight
@@ -743,7 +743,7 @@ Popup {
NText {
text: model.fileIsDir ? "" : root.formatFileSize(model.fileSize)
color: filePickerPanel.currentSelection.includes(model.filePath) ? Color.mOnSecondary : Color.mOnSurfaceVariant
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
visible: !model.fileIsDir
Layout.preferredWidth: implicitWidth
}
@@ -795,7 +795,7 @@ Popup {
// Footer
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NText {
text: {
@@ -809,12 +809,12 @@ Popup {
}
}
color: filePickerPanel.searchText.length > 0 ? Color.mPrimary : Color.mOnSurfaceVariant
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
Layout.fillWidth: true
}
NButton {
- text: "Cancel"
+ text: I18n.tr("widgets.file-picker.cancel")
outlined: true
onClicked: {
root.cancelled()
diff --git a/Widgets/NHeader.qml b/Widgets/NHeader.qml
index dfbcfd54..08d58e01 100644
--- a/Widgets/NHeader.qml
+++ b/Widgets/NHeader.qml
@@ -8,13 +8,13 @@ ColumnLayout {
property string label: ""
property string description: ""
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
Layout.fillWidth: true
- Layout.bottomMargin: Style.marginM * scaling
+ Layout.bottomMargin: Style.marginM
NText {
text: root.label
- pointSize: Style.fontSizeXL * scaling
+ pointSize: Style.fontSizeXL
font.weight: Style.fontWeightBold
color: Color.mSecondary
visible: root.label !== ""
@@ -22,7 +22,7 @@ ColumnLayout {
NText {
text: root.description
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
diff --git a/Widgets/NIcon.qml b/Widgets/NIcon.qml
index adca75f6..87e63c1f 100644
--- a/Widgets/NIcon.qml
+++ b/Widgets/NIcon.qml
@@ -7,7 +7,8 @@ Text {
id: root
property string icon: Icons.defaultIcon
- property real pointSize: Style.fontSizeL * scaling
+ property real pointSize: Style.fontSizeL
+ property bool applyUiScale: true
visible: (icon !== undefined) && (icon !== "")
text: {
@@ -22,7 +23,7 @@ Text {
return Icons.get(icon)
}
font.family: Icons.fontFamily
- font.pointSize: root.pointSize
+ font.pointSize: applyUiScale ? root.pointSize * Style.uiScaleRatio : root.pointSize
color: Color.mOnSurface
verticalAlignment: Text.AlignVCenter
}
diff --git a/Widgets/NIconButton.qml b/Widgets/NIconButton.qml
index e2d0d0fa..4a61c1df 100644
--- a/Widgets/NIconButton.qml
+++ b/Widgets/NIconButton.qml
@@ -8,14 +8,15 @@ Rectangle {
id: root
property real baseSize: Style.baseWidgetSize
+ property bool applyUiScale: true
property string icon
property string tooltipText
property string tooltipDirection: "auto"
+ property string density: ""
property bool enabled: true
property bool allowClickWhenDisabled: false
property bool hovering: false
- property bool compact: false
property color colorBg: Color.mSurfaceVariant
property color colorFg: Color.mPrimary
@@ -30,14 +31,14 @@ Rectangle {
signal rightClicked
signal middleClicked
- implicitWidth: Math.round(baseSize * scaling)
- implicitHeight: Math.round(baseSize * scaling)
+ implicitWidth: applyUiScale ? Math.round(baseSize * Style.uiScaleRatio) : Math.round(baseSize)
+ implicitHeight: applyUiScale ? Math.round(baseSize * Style.uiScaleRatio) : Math.round(baseSize)
opacity: root.enabled ? Style.opacityFull : Style.opacityMedium
color: root.enabled && root.hovering ? colorBgHover : colorBg
radius: width * 0.5
border.color: root.enabled && root.hovering ? colorBorderHover : colorBorder
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
Behavior on color {
ColorAnimation {
@@ -48,7 +49,15 @@ Rectangle {
NIcon {
icon: root.icon
- pointSize: Math.max(1, root.compact ? root.width * 0.65 : root.width * 0.48)
+ pointSize: {
+ switch (root.density) {
+ case "compact":
+ return Math.max(1, root.width * 0.65)
+ default:
+ return Math.max(1, root.width * 0.48)
+ }
+ }
+ applyUiScale: root.applyUiScale
color: root.enabled && root.hovering ? colorFgHover : colorFg
// Center horizontally
x: (root.width - width) / 2
diff --git a/Widgets/NIconButtonHot.qml b/Widgets/NIconButtonHot.qml
new file mode 100644
index 00000000..18df3bac
--- /dev/null
+++ b/Widgets/NIconButtonHot.qml
@@ -0,0 +1,174 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Effects
+import qs.Commons
+import qs.Services
+
+Rectangle {
+ id: root
+
+ // Public properties
+ property real baseSize: Style.baseWidgetSize
+ property bool applyUiScale: true
+ property string icon: ""
+ property string tooltipText: ""
+ property string tooltipDirection: "auto"
+ property string density: ""
+ property bool enabled: true
+ property bool allowClickWhenDisabled: false
+ property bool hot: false
+
+ // Internal properties
+ property bool hovering: false
+ property bool pressed: false
+
+ // Color properties
+ property color colorBg: Color.mSurfaceVariant
+ property color colorFg: Color.mPrimary
+ property color colorBgHover: Color.mTertiary
+ property color colorFgHover: Color.mOnTertiary
+ property color colorBorder: Color.mOutline
+ property color colorBorderHover: Color.mOutline
+
+ // Hot state colors
+ property color colorBgHot: Color.mPrimary
+ property color colorFgHot: Color.mOnPrimary
+
+ // Signals
+ signal entered
+ signal exited
+ signal clicked
+ signal rightClicked
+ signal middleClicked
+
+ // Dimensions
+ implicitWidth: applyUiScale ? Math.round(baseSize * Style.uiScaleRatio) : Math.round(baseSize)
+ implicitHeight: applyUiScale ? Math.round(baseSize * Style.uiScaleRatio) : Math.round(baseSize)
+
+ // Appearance
+ opacity: root.enabled ? Style.opacityFull : Style.opacityMedium
+ color: {
+ if (pressed) {
+ return colorBgHover
+ }
+ if (hot) {
+ return colorBgHot
+ }
+ if (root.enabled && root.hovering) {
+ return colorBgHover
+ }
+ return colorBg
+ }
+ radius: width * 0.5
+ border.color: root.enabled && root.hovering ? colorBorderHover : colorBorder
+ border.width: Math.max(1, Style.borderS)
+
+ Behavior on color {
+ ColorAnimation {
+ duration: Style.animationNormal
+ easing.type: Easing.InOutQuad
+ }
+ }
+
+ Behavior on scale {
+ NumberAnimation {
+ duration: Style.animationFast
+ easing.type: Easing.OutCubic
+ }
+ }
+
+ // Icon
+ NIcon {
+ icon: root.icon
+ pointSize: Math.max(1, Math.round(root.width * 0.48))
+ applyUiScale: root.applyUiScale
+ color: {
+ if (pressed) {
+ return colorFgHover
+ }
+ if (hot) {
+ return colorFgHot
+ }
+ if (root.enabled && root.hovering) {
+ return colorFgHover
+ }
+ return colorFg
+ }
+ // Center horizontally
+ x: (root.width - width) / 2
+ // Center vertically accounting for font metrics
+ y: (root.height - height) / 2 + (height - contentHeight) / 2
+
+ Behavior on color {
+ ColorAnimation {
+ duration: Style.animationFast
+ easing.type: Easing.InOutQuad
+ }
+ }
+ }
+
+ MouseArea {
+ // Always enabled to allow hover/tooltip even when the button is disabled
+ enabled: true
+ anchors.fill: parent
+ cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
+ acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
+ hoverEnabled: true
+
+ onEntered: {
+ hovering = root.enabled ? true : false
+ if (tooltipText) {
+ TooltipService.show(Screen, parent, tooltipText, tooltipDirection)
+ }
+ root.entered()
+ }
+
+ onExited: {
+ hovering = false
+ if (tooltipText) {
+ TooltipService.hide()
+ }
+ root.exited()
+ }
+
+ onPressed: function (mouse) {
+ if (root.enabled) {
+ root.pressed = true
+ root.scale = 0.92
+ }
+ if (tooltipText) {
+ TooltipService.hide()
+ }
+ }
+
+ onReleased: function (mouse) {
+ root.scale = 1.0
+ root.pressed = false
+
+ if (!root.enabled && !allowClickWhenDisabled) {
+ return
+ }
+
+ // Only trigger actions if released while hovering
+ if (root.hovering) {
+ if (mouse.button === Qt.LeftButton) {
+ root.clicked()
+ } else if (mouse.button === Qt.RightButton) {
+ root.rightClicked()
+ } else if (mouse.button === Qt.MiddleButton) {
+ root.middleClicked()
+ }
+ }
+ }
+
+ onCanceled: {
+ root.hovering = false
+ root.pressed = false
+ root.scale = 1.0
+ if (tooltipText) {
+ TooltipService.hide()
+ }
+ }
+ }
+}
diff --git a/Widgets/NIconPicker.qml b/Widgets/NIconPicker.qml
index 6a5cc45d..2a4916e9 100644
--- a/Widgets/NIconPicker.qml
+++ b/Widgets/NIconPicker.qml
@@ -15,18 +15,10 @@ Popup {
signal iconSelected(string iconName)
- width: {
- var w = Math.round(Math.max(screen.width * 0.35, 900) * scaling)
- w = Math.min(w, screen.width - Style.marginL * 2)
- return w
- }
- height: {
- var h = Math.round(Math.max(screen.height * 0.65, 700) * scaling)
- h = Math.min(h, screen.height - Style.barHeight * scaling - Style.marginL * 2)
- return h
- }
+ width: 900 * Style.uiScaleRatio
+ height: 700 * Style.uiScaleRatio
anchors.centerIn: Overlay.overlay
- padding: Style.marginXL * scaling
+ padding: Style.marginXL
property string query: ""
property var allIcons: Object.keys(Icons.icons)
@@ -38,7 +30,7 @@ Popup {
}
readonly property int columns: 6
readonly property int cellW: Math.floor(grid.width / columns)
- readonly property int cellH: Math.round(cellW * 0.7 + 36 * scaling)
+ readonly property int cellH: Math.round(cellW * 0.7 + 36)
onOpened: {
selectedIcon = initialIcon
@@ -53,21 +45,21 @@ Popup {
background: Rectangle {
color: Color.mSurface
- radius: Style.radiusL * scaling
+ radius: Style.radiusL
border.color: Color.mPrimary
- border.width: Style.borderM * scaling
+ border.width: Style.borderM
}
ColumnLayout {
anchors.fill: parent
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
// Title row
RowLayout {
Layout.fillWidth: true
NText {
text: I18n.tr("widgets.icon-picker.title")
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mPrimary
Layout.fillWidth: true
@@ -84,7 +76,7 @@ Popup {
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
NTextInput {
id: searchInput
Layout.fillWidth: true
@@ -100,7 +92,7 @@ Popup {
id: grid
Layout.fillWidth: true
Layout.fillHeight: true
- Layout.margins: Style.marginM * scaling
+ Layout.margins: Style.marginM
cellWidth: root.cellW
cellHeight: root.cellH
model: root.filteredIcons
@@ -109,11 +101,11 @@ Popup {
delegate: Rectangle {
width: grid.cellWidth
height: grid.cellHeight
- radius: Style.radiusS * scaling
+ radius: Style.radiusS
color: (root.selectedIcon === modelData) ? Qt.alpha(Color.mPrimary, 0.15) : Color.transparent
border.color: (root.selectedIcon === modelData) ? Color.mPrimary : Qt.rgba(0, 0, 0, 0)
- border.width: (root.selectedIcon === modelData) ? Style.borderS * scaling : 0
+ border.width: (root.selectedIcon === modelData) ? Style.borderS : 0
MouseArea {
anchors.fill: parent
@@ -127,27 +119,27 @@ Popup {
ColumnLayout {
anchors.fill: parent
- anchors.margins: Style.marginS * scaling
- spacing: Style.marginS * scaling
+ anchors.margins: Style.marginS
+ spacing: Style.marginS
Item {
Layout.fillHeight: true
- Layout.preferredHeight: 4 * scaling
+ Layout.preferredHeight: 4
}
NIcon {
Layout.alignment: Qt.AlignHCenter
icon: modelData
- pointSize: 42 * scaling
+ pointSize: 42
}
NText {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
- Layout.topMargin: Style.marginXS * scaling
+ Layout.topMargin: Style.marginXS
elide: Text.ElideRight
wrapMode: Text.NoWrap
maximumLineCount: 1
horizontalAlignment: Text.AlignHCenter
color: Color.mOnSurfaceVariant
- pointSize: Style.fontSizeXS * scaling
+ pointSize: Style.fontSizeXS
text: modelData
}
Item {
@@ -159,7 +151,7 @@ Popup {
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
Item {
Layout.fillWidth: true
}
diff --git a/Widgets/NImageCircled.qml b/Widgets/NImageCircled.qml
index eb0ee9bd..55ef7568 100644
--- a/Widgets/NImageCircled.qml
+++ b/Widgets/NImageCircled.qml
@@ -12,11 +12,11 @@ Rectangle {
property color borderColor: Color.transparent
property real borderWidth: 0
property string fallbackIcon: ""
- property real fallbackIconSize: Style.fontSizeXXL * scaling
+ property real fallbackIconSize: Style.fontSizeXXL
color: Color.transparent
radius: parent.width * 0.5
- anchors.margins: Style.marginXXS * scaling
+ anchors.margins: Style.marginXXS
Rectangle {
color: Color.transparent
diff --git a/Widgets/NImageRounded.qml b/Widgets/NImageRounded.qml
index ff55a571..32987e28 100644
--- a/Widgets/NImageRounded.qml
+++ b/Widgets/NImageRounded.qml
@@ -13,7 +13,7 @@ Rectangle {
property real borderWidth: 0
property real imageRadius: width * 0.5
property string fallbackIcon: ""
- property real fallbackIconSize: Style.fontSizeXXL * scaling
+ property real fallbackIconSize: Style.fontSizeXXL
property real scaledRadius: imageRadius * Settings.data.general.radiusRatio
@@ -21,7 +21,7 @@ Rectangle {
color: Color.transparent
radius: scaledRadius
- anchors.margins: Style.marginXXS * scaling
+ anchors.margins: Style.marginXXS
Rectangle {
color: Color.transparent
diff --git a/Widgets/NInputAction.qml b/Widgets/NInputAction.qml
index d689d304..a1e683da 100644
--- a/Widgets/NInputAction.qml
+++ b/Widgets/NInputAction.qml
@@ -22,8 +22,7 @@ RowLayout {
signal actionClicked
// Internal properties
- property real scaling: 1.0
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NTextInput {
id: textInput
diff --git a/Widgets/NLabel.qml b/Widgets/NLabel.qml
index 8348fd0c..f1a42440 100644
--- a/Widgets/NLabel.qml
+++ b/Widgets/NLabel.qml
@@ -10,12 +10,12 @@ ColumnLayout {
property color labelColor: Color.mOnSurface
property color descriptionColor: Color.mOnSurfaceVariant
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
Layout.fillWidth: true
NText {
text: label
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: labelColor
visible: label !== ""
@@ -24,7 +24,7 @@ ColumnLayout {
NText {
text: description
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
color: descriptionColor
wrapMode: Text.WordWrap
visible: description !== ""
diff --git a/Widgets/NListView.qml b/Widgets/NListView.qml
index 6731d7ce..115bac49 100644
--- a/Widgets/NListView.qml
+++ b/Widgets/NListView.qml
@@ -10,8 +10,8 @@ Item {
property color handleHoverColor: handleColor
property color handlePressedColor: handleColor
property color trackColor: Color.transparent
- property real handleWidth: 6 * scaling
- property real handleRadius: Style.radiusM * scaling
+ property real handleWidth: 6
+ property real handleRadius: Style.radiusM
property int verticalPolicy: ScrollBar.AsNeeded
property int horizontalPolicy: ScrollBar.AsNeeded
diff --git a/Widgets/NPanel.qml b/Widgets/NPanel.qml
index 9c7bf2db..5651d8af 100644
--- a/Widgets/NPanel.qml
+++ b/Widgets/NPanel.qml
@@ -8,7 +8,6 @@ Loader {
id: root
property ShellScreen screen
- property real scaling: 1.0
property Component panelContent: null
property real preferredWidth: 700
@@ -27,8 +26,6 @@ Loader {
property bool panelAnchorLeft: false
property bool panelAnchorRight: false
- property bool isMasked: false
-
// Properties to support positioning relative to the opener (button)
property bool useButtonPosition: false
property point buttonPosition: Qt.point(0, 0)
@@ -39,10 +36,8 @@ Loader {
property bool backgroundClickEnabled: true
// Animation properties
- readonly property real originalScale: 0.7
- readonly property real originalOpacity: 0.0
+ readonly property real originalScale: 0.0
property real scaleValue: originalScale
- property real opacityValue: originalOpacity
property real dimmingOpacity: 0
signal opened
@@ -100,7 +95,6 @@ Loader {
function close() {
dimmingOpacity = 0
scaleValue = originalScale
- opacityValue = originalOpacity
root.closed()
active = false
useButtonPosition = false
@@ -131,58 +125,40 @@ Loader {
// -----------------------------------------
sourceComponent: Component {
+ // PanelWindow has its own screen property inherited of QsWindow
PanelWindow {
id: panelWindow
- // PanelWindow has its own screen property inherited of QsWindow
- property real scaling: ScalingService.getScreenScale(screen)
-
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
readonly property bool barIsVisible: (screen !== null) && (Settings.data.bar.monitors.includes(screen.name) || (Settings.data.bar.monitors.length === 0))
- readonly property real verticalBarWidth: Math.round(Style.barHeight * scaling)
+ readonly property real verticalBarWidth: Style.barHeight
Component.onCompleted: {
- Logger.log("NPanel", "Opened", root.objectName)
+ Logger.log("NPanel", "Opened", root.objectName, "on", screen.name)
dimmingOpacity = Style.opacityHeavy
}
- Connections {
- target: ScalingService
- function onScaleChanged(screenName, scale) {
- if ((screen !== null) && (screenName === screen.name)) {
- root.scaling = scaling = scale
- }
- }
- }
-
Connections {
target: panelWindow
function onScreenChanged() {
root.screen = screen
- root.scaling = scaling = ScalingService.getScreenScale(screen)
-
- // It's mandatory to force refresh the subloader to ensure the scaling is properly dispatched
- panelContentLoader.active = false
- panelContentLoader.active = true
// If called from IPC always reposition if screen is updated
if (buttonName) {
setPosition()
}
- // Logger.log("NPanel", "OnScreenChanged", root.screen.name)
+ Logger.log("NPanel", "OnScreenChanged", root.screen.name)
}
}
visible: true
- color: Settings.data.general.dimDesktop && !root.isMasked ? Qt.alpha(Color.mShadow, dimmingOpacity) : Color.transparent
+ color: Settings.data.general.dimDesktop ? Qt.alpha(Color.mShadow, dimmingOpacity) : Color.transparent
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.namespace: "noctalia-panel"
WlrLayershell.keyboardFocus: root.panelKeyboardFocus ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
- mask: root.isMasked ? maskRegion : null
-
Region {
id: maskRegion
}
@@ -217,9 +193,9 @@ Loader {
Rectangle {
id: panelBackground
color: panelBackgroundColor
- radius: Style.radiusL * scaling
+ radius: Style.radiusL
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
// Dragging support
property bool draggable: root.draggable
property bool isDragged: false
@@ -228,9 +204,9 @@ Loader {
width: {
var w
if (preferredWidthRatio !== undefined) {
- w = Math.round(Math.max(screen?.width * preferredWidthRatio, preferredWidth) * scaling)
+ w = Math.round(Math.max(screen?.width * preferredWidthRatio, preferredWidth))
} else {
- w = preferredWidth * scaling
+ w = preferredWidth
}
// Clamp width so it is never bigger than the screen
return Math.min(w, screen?.width - Style.marginL * 2)
@@ -238,17 +214,16 @@ Loader {
height: {
var h
if (preferredHeightRatio !== undefined) {
- h = Math.round(Math.max(screen?.height * preferredHeightRatio, preferredHeight) * scaling)
+ h = Math.round(Math.max(screen?.height * preferredHeightRatio, preferredHeight))
} else {
- h = preferredHeight * scaling
+ h = preferredHeight
}
// Clamp width so it is never bigger than the screen
- return Math.min(h, screen?.height - Style.barHeight * scaling - Style.marginL * 2)
+ return Math.min(h, screen?.height - Style.barHeight - Style.marginL * 2)
}
scale: root.scaleValue
- opacity: root.isMasked ? 0 : root.opacityValue
x: isDragged ? manualX : calculatedX
y: isDragged ? manualY : calculatedY
@@ -261,9 +236,9 @@ Loader {
}
switch (barPosition) {
case "top":
- return (Style.barHeight + Style.marginS) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0)
+ return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0)
default:
- return Style.marginS * scaling
+ return Style.marginS
}
}
@@ -273,9 +248,9 @@ Loader {
}
switch (barPosition) {
case "bottom":
- return (Style.barHeight + Style.marginS) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0)
+ return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0)
default:
- return Style.marginS * scaling
+ return Style.marginS
}
}
@@ -285,9 +260,9 @@ Loader {
}
switch (barPosition) {
case "left":
- return (Style.barHeight + Style.marginS) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0)
+ return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0)
default:
- return Style.marginS * scaling
+ return Style.marginS
}
}
@@ -297,9 +272,9 @@ Loader {
}
switch (barPosition) {
case "right":
- return (Style.barHeight + Style.marginS) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0)
+ return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0)
default:
- return Style.marginS * scaling
+ return Style.marginS
}
}
@@ -388,7 +363,6 @@ Loader {
// Animate in when component is completed
Component.onCompleted: {
root.scaleValue = 1.0
- root.opacityValue = 1.0
}
// Reset drag position when panel closes
@@ -454,14 +428,14 @@ Loader {
var ny = dragStartY + translation.y
// Calculate gaps so we never overlap the bar on any side
- var baseGap = Style.marginS * scaling
- var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * 2 * Style.marginXL * scaling : 0
- var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * 2 * Style.marginXL * scaling : 0
+ var baseGap = Style.marginS
+ var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * 2 * Style.marginXL : 0
+ var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * 2 * Style.marginXL : 0
- var insetLeft = baseGap + ((barIsVisible && barPosition === "left") ? (Style.barHeight * scaling + floatExtraH) : 0)
- var insetRight = baseGap + ((barIsVisible && barPosition === "right") ? (Style.barHeight * scaling + floatExtraH) : 0)
- var insetTop = baseGap + ((barIsVisible && barPosition === "top") ? (Style.barHeight * scaling + floatExtraV) : 0)
- var insetBottom = baseGap + ((barIsVisible && barPosition === "bottom") ? (Style.barHeight * scaling + floatExtraV) : 0)
+ var insetLeft = baseGap + ((barIsVisible && barPosition === "left") ? (Style.barHeight + floatExtraH) : 0)
+ var insetRight = baseGap + ((barIsVisible && barPosition === "right") ? (Style.barHeight + floatExtraH) : 0)
+ var insetTop = baseGap + ((barIsVisible && barPosition === "top") ? (Style.barHeight + floatExtraV) : 0)
+ var insetBottom = baseGap + ((barIsVisible && barPosition === "bottom") ? (Style.barHeight + floatExtraV) : 0)
// Clamp within screen bounds accounting for insets
var maxX = panelWindow.width - panelBackground.width - insetRight
@@ -480,7 +454,7 @@ Loader {
anchors.margins: 0
color: Color.transparent
border.color: Color.mPrimary
- border.width: Math.max(2, Style.borderL * scaling)
+ border.width: Math.max(2, Style.borderL)
radius: parent.radius
visible: panelBackground.isDragged && dragHandler.active
opacity: 0.8
@@ -492,7 +466,7 @@ Loader {
anchors.margins: 0
color: Color.transparent
border.color: Color.mPrimary
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
radius: parent.radius
opacity: 0.3
}
diff --git a/Widgets/NRadioButton.qml b/Widgets/NRadioButton.qml
index d04ea777..59ff2205 100644
--- a/Widgets/NRadioButton.qml
+++ b/Widgets/NRadioButton.qml
@@ -10,12 +10,12 @@ RadioButton {
indicator: Rectangle {
id: outerCircle
- implicitWidth: Style.baseWidgetSize * 0.625 * scaling
- implicitHeight: Style.baseWidgetSize * 0.625 * scaling
+ implicitWidth: Style.baseWidgetSize * 0.625
+ implicitHeight: Style.baseWidgetSize * 0.625
radius: width * 0.5
color: Color.transparent
border.color: root.checked ? Color.mPrimary : Color.mOnSurface
- border.width: Math.max(1, Style.borderM * scaling)
+ border.width: Math.max(1, Style.borderM)
anchors.verticalCenter: parent.verticalCenter
Rectangle {
@@ -41,10 +41,10 @@ RadioButton {
contentItem: NText {
text: root.text
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
anchors.verticalCenter: parent.verticalCenter
anchors.left: outerCircle.right
anchors.right: parent.right
- anchors.leftMargin: Style.marginS * scaling
+ anchors.leftMargin: Style.marginS
}
}
diff --git a/Widgets/NReorderCheckboxes.qml b/Widgets/NReorderCheckboxes.qml
new file mode 100644
index 00000000..9a8c1abc
--- /dev/null
+++ b/Widgets/NReorderCheckboxes.qml
@@ -0,0 +1,299 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import qs.Commons
+
+Item {
+ id: root
+
+ // Public API
+ property var model: []
+ property color activeColor: Color.mPrimary
+ property color activeOnColor: Color.mOnPrimary
+ property color dragHandleColor: Color.mOutline
+ property int baseSize: Style.baseWidgetSize * 0.7
+ property int spacing: Style.marginM
+
+ signal itemToggled(int index, bool enabled)
+ signal itemsReordered(int fromIndex, int toIndex)
+ signal dragPotentialStarted
+ signal dragPotentialEnded
+
+ implicitHeight: listView.contentHeight
+
+ function toggleItem(index) {
+ if (index < 0 || index >= root.model.length)
+ return
+
+ var item = root.model[index]
+ if (item.required)
+ return
+
+ // Create a new array to trigger binding update
+ var newModel = root.model.slice()
+ newModel[index] = Object.assign({}, item, {
+ "enabled": !item.enabled
+ })
+ root.model = newModel
+
+ root.itemToggled(index, newModel[index].enabled)
+ }
+
+ function moveItem(fromIndex, toIndex) {
+ if (fromIndex === toIndex)
+ return
+ if (fromIndex < 0 || fromIndex >= root.model.length)
+ return
+ if (toIndex < 0 || toIndex >= root.model.length)
+ return
+
+ // Create a new array with item moved
+ var newModel = root.model.slice()
+ var item = newModel.splice(fromIndex, 1)[0]
+ newModel.splice(toIndex, 0, item)
+ root.model = newModel
+
+ root.itemsReordered(fromIndex, toIndex)
+ }
+
+ ListView {
+ id: listView
+
+ anchors.fill: parent
+ spacing: root.spacing
+ interactive: false
+ clip: true
+ model: root.model
+
+ delegate: Item {
+ id: delegateItem
+
+ width: listView.width
+ height: checkboxRow.height
+
+ required property int index
+ required property var modelData
+
+ property string text: modelData.text || ""
+ property bool enabled: modelData.enabled || false
+ property bool required: modelData.required || false
+ property bool dragging: false
+ property int dragStartY: 0
+ property int dragStartIndex: -1
+ property int dragTargetIndex: -1
+ property int itemSpacing: root.spacing
+
+ RowLayout {
+ id: checkboxRow
+
+ width: parent.width
+ spacing: Style.marginS
+
+ // Drag handle
+ Rectangle {
+ id: dragHandle
+
+ Layout.preferredWidth: root.baseSize
+ Layout.preferredHeight: root.baseSize
+ radius: Style.radiusXS
+ color: dragHandleMouseArea.containsMouse ? Color.mSurfaceVariant : Color.transparent
+
+ Behavior on color {
+ ColorAnimation {
+ duration: Style.animationFast
+ }
+ }
+
+ ColumnLayout {
+ anchors.centerIn: parent
+ spacing: Style.marginS
+
+ Repeater {
+ model: 3
+ Rectangle {
+ Layout.preferredWidth: root.baseSize * 0.4
+ Layout.preferredHeight: 2
+ radius: 1
+ color: root.dragHandleColor
+ }
+ }
+ }
+
+ MouseArea {
+ id: dragHandleMouseArea
+
+ anchors.fill: parent
+ cursorShape: Qt.SizeVerCursor
+ hoverEnabled: true
+ preventStealing: false
+ z: 1000
+
+ onPressed: mouse => {
+ delegateItem.dragStartIndex = delegateItem.index
+ delegateItem.dragTargetIndex = delegateItem.index
+ delegateItem.dragStartY = delegateItem.y
+ delegateItem.dragging = true
+ delegateItem.z = 999
+
+ // Signal that interaction started (prevents panel close)
+ preventStealing = true
+ root.dragPotentialStarted()
+ }
+
+ onPositionChanged: mouse => {
+ if (delegateItem.dragging) {
+ var dy = mouse.y - dragHandle.height / 2
+ var newY = delegateItem.y + dy
+
+ // Constrain within bounds
+ newY = Math.max(0, Math.min(newY, listView.contentHeight - delegateItem.height))
+ delegateItem.y = newY
+
+ // Calculate target index (but don't apply yet)
+ var targetIndex = Math.floor((newY + delegateItem.height / 2) / (delegateItem.height + delegateItem.itemSpacing))
+ targetIndex = Math.max(0, Math.min(targetIndex, listView.count - 1))
+
+ delegateItem.dragTargetIndex = targetIndex
+ }
+ }
+
+ onReleased: {
+ // Always signal end of interaction
+ preventStealing = false
+ root.dragPotentialEnded()
+
+ // Apply the model change now that drag is complete
+ if (delegateItem.dragStartIndex !== -1 && delegateItem.dragTargetIndex !== -1 && delegateItem.dragStartIndex !== delegateItem.dragTargetIndex) {
+ root.moveItem(delegateItem.dragStartIndex, delegateItem.dragTargetIndex)
+ }
+
+ delegateItem.dragging = false
+ delegateItem.dragStartIndex = -1
+ delegateItem.dragTargetIndex = -1
+ delegateItem.z = 0
+ }
+
+ onCanceled: {
+ // Handle cancel (e.g., ESC key pressed during drag)
+ preventStealing = false
+ root.dragPotentialEnded()
+
+ delegateItem.dragging = false
+ delegateItem.dragStartIndex = -1
+ delegateItem.dragTargetIndex = -1
+ delegateItem.z = 0
+ }
+ }
+ }
+
+ // Checkbox
+ Rectangle {
+ id: box
+
+ Layout.preferredWidth: root.baseSize
+ Layout.preferredHeight: root.baseSize
+ radius: Style.radiusXS
+ color: delegateItem.enabled ? root.activeColor : Color.mSurface
+ border.color: delegateItem.required ? root.activeColor : Color.mOutline
+ border.width: Math.max(1, Style.borderS)
+ opacity: delegateItem.required ? 0.7 : 1.0
+
+ Behavior on color {
+ ColorAnimation {
+ duration: Style.animationFast
+ }
+ }
+
+ Behavior on border.color {
+ ColorAnimation {
+ duration: Style.animationFast
+ }
+ }
+
+ NIcon {
+ visible: delegateItem.enabled
+ anchors.centerIn: parent
+ anchors.horizontalCenterOffset: -1
+ icon: "check"
+ color: root.activeOnColor
+ pointSize: Math.max(Style.fontSizeXS, root.baseSize * 0.5)
+ font.weight: Style.fontWeightBold
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ cursorShape: delegateItem.required ? Qt.ForbiddenCursor : Qt.PointingHandCursor
+ enabled: !delegateItem.required
+
+ onClicked: {
+ root.toggleItem(delegateItem.index)
+ }
+ }
+ }
+
+ // Label
+ NText {
+ Layout.fillWidth: true
+ text: delegateItem.text
+ color: Color.mOnSurface
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ // Required indicator
+ NText {
+ visible: delegateItem.required
+ text: "(required)"
+ color: Color.mOnSurfaceVariant
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+
+ // Position binding for non-dragging state
+ y: {
+ if (delegateItem.dragging) {
+ return delegateItem.y
+ }
+
+ // Check if any item is being dragged
+ var draggedIndex = -1
+ var targetIndex = -1
+ for (var i = 0; i < listView.count; i++) {
+ var item = listView.itemAtIndex(i)
+ if (item && item.dragging) {
+ draggedIndex = item.dragStartIndex
+ targetIndex = item.dragTargetIndex
+ break
+ }
+ }
+
+ // If an item is being dragged, adjust positions
+ if (draggedIndex !== -1 && targetIndex !== -1 && draggedIndex !== targetIndex) {
+ var currentIndex = delegateItem.index
+
+ if (draggedIndex < targetIndex) {
+ // Dragging down: shift items up between draggedIndex and targetIndex
+ if (currentIndex > draggedIndex && currentIndex <= targetIndex) {
+ return (currentIndex - 1) * (delegateItem.height + delegateItem.itemSpacing)
+ }
+ } else {
+ // Dragging up: shift items down between targetIndex and draggedIndex
+ if (currentIndex >= targetIndex && currentIndex < draggedIndex) {
+ return (currentIndex + 1) * (delegateItem.height + delegateItem.itemSpacing)
+ }
+ }
+ }
+
+ return delegateItem.index * (delegateItem.height + delegateItem.itemSpacing)
+ }
+
+ Behavior on y {
+ enabled: !delegateItem.dragging
+ NumberAnimation {
+ duration: Style.animationNormal
+ easing.type: Easing.OutQuad
+ }
+ }
+ }
+ }
+}
diff --git a/Widgets/NScrollView.qml b/Widgets/NScrollView.qml
index 63e28819..af5371d9 100644
--- a/Widgets/NScrollView.qml
+++ b/Widgets/NScrollView.qml
@@ -10,8 +10,8 @@ T.ScrollView {
property color handleHoverColor: handleColor
property color handlePressedColor: handleColor
property color trackColor: Color.transparent
- property real handleWidth: 6 * scaling
- property real handleRadius: Style.radiusM * scaling
+ property real handleWidth: 6
+ property real handleRadius: Style.radiusM
property int verticalPolicy: ScrollBar.AsNeeded
property int horizontalPolicy: ScrollBar.AsNeeded
property bool preventHorizontalScroll: horizontalPolicy === ScrollBar.AlwaysOff
diff --git a/Widgets/NSearchableComboBox.qml b/Widgets/NSearchableComboBox.qml
index 3175f126..ffb4f4cc 100644
--- a/Widgets/NSearchableComboBox.qml
+++ b/Widgets/NSearchableComboBox.qml
@@ -9,8 +9,8 @@ import "../Helpers/FuzzySort.js" as Fuzzysort
RowLayout {
id: root
- property real minimumWidth: 280 * scaling
- property real popupHeight: 180 * scaling
+ property real minimumWidth: 280 * Style.uiScaleRatio
+ property real popupHeight: 180 * Style.uiScaleRatio
property string label: ""
property string description: ""
@@ -20,12 +20,13 @@ RowLayout {
property string currentKey: ""
property string placeholder: ""
property string searchPlaceholder: I18n.tr("placeholders.search")
+ property Component delegate: null
- readonly property real preferredHeight: Style.baseWidgetSize * 1.1 * scaling
+ readonly property real preferredHeight: Style.baseWidgetSize * 1.1
signal selected(string key)
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
Layout.fillWidth: true
// Filtered model for search results
@@ -121,12 +122,12 @@ RowLayout {
}
background: Rectangle {
- implicitWidth: Style.baseWidgetSize * 3.75 * scaling
+ implicitWidth: Style.baseWidgetSize * 3.75 * Style.uiScaleRatio
implicitHeight: preferredHeight
color: Color.mSurface
border.color: combo.activeFocus ? Color.mSecondary : Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
- radius: Style.radiusM * scaling
+ border.width: Math.max(1, Style.borderS)
+ radius: Style.radiusM
Behavior on border.color {
ColorAnimation {
@@ -136,9 +137,9 @@ RowLayout {
}
contentItem: NText {
- leftPadding: Style.marginL * scaling
- rightPadding: combo.indicator.width + Style.marginL * scaling
- pointSize: Style.fontSizeM * scaling
+ leftPadding: Style.marginL
+ rightPadding: combo.indicator.width + Style.marginL
+ pointSize: Style.fontSizeM
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
color: (combo.currentIndex >= 0 && combo.currentIndex < filteredModel.count) ? Color.mOnSurface : Color.mOnSurfaceVariant
@@ -146,17 +147,17 @@ RowLayout {
}
indicator: NIcon {
- x: combo.width - width - Style.marginM * scaling
+ x: combo.width - width - Style.marginM
y: combo.topPadding + (combo.availableHeight - height) / 2
icon: "caret-down"
- pointSize: Style.fontSizeL * scaling
+ pointSize: Style.fontSizeL
}
popup: Popup {
y: combo.height
width: combo.width
- height: root.popupHeight + 60 * scaling
- padding: Style.marginM * scaling
+ height: root.popupHeight + 60
+ padding: Style.marginM
onOpened: {
PanelService.willOpenPopup(root)
@@ -167,7 +168,7 @@ RowLayout {
}
contentItem: ColumnLayout {
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
// Search input
NTextInput {
@@ -176,7 +177,7 @@ RowLayout {
placeholderText: root.searchPlaceholder
text: root.searchText
onTextChanged: root.searchText = text
- fontSize: Style.fontSizeS * scaling
+ fontSize: Style.fontSizeS
}
NListView {
@@ -187,43 +188,79 @@ RowLayout {
horizontalPolicy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AsNeeded
- delegate: ItemDelegate {
- width: listView.width
- hoverEnabled: true
- highlighted: ListView.view.currentIndex === index
+ delegate: root.delegate ? root.delegate : defaultDelegate
- onHoveredChanged: {
- if (hovered) {
- ListView.view.currentIndex = index
- }
- }
+ Component {
+ id: defaultDelegate
+ ItemDelegate {
+ id: delegateRoot
+ width: listView.width
+ hoverEnabled: true
+ highlighted: ListView.view.currentIndex === index
- onClicked: {
- root.selected(filteredModel.get(index).key)
- combo.currentIndex = root.findIndexByKeyInFiltered(filteredModel.get(index).key)
- combo.popup.close()
- }
-
- contentItem: NText {
- text: name
- pointSize: Style.fontSizeM * scaling
- color: highlighted ? Color.mSurface : Color.mOnSurface
- verticalAlignment: Text.AlignVCenter
- elide: Text.ElideRight
- Behavior on color {
- ColorAnimation {
- duration: Style.animationFast
+ onHoveredChanged: {
+ if (hovered) {
+ ListView.view.currentIndex = index
}
}
- }
- background: Rectangle {
- width: listView.width * scaling
- color: highlighted ? Color.mTertiary : Color.transparent
- radius: Style.radiusS * scaling
- Behavior on color {
- ColorAnimation {
- duration: Style.animationFast
+ onClicked: {
+ root.selected(filteredModel.get(index).key)
+ combo.currentIndex = root.findIndexByKeyInFiltered(filteredModel.get(index).key)
+ combo.popup.close()
+ }
+
+ contentItem: RowLayout {
+ width: parent.width
+ spacing: Style.marginM
+
+ NText {
+ text: name
+ pointSize: Style.fontSizeM
+ color: highlighted ? Color.mOnTertiary : Color.mOnSurface
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ Layout.fillWidth: true
+ Behavior on color {
+ ColorAnimation {
+ duration: Style.animationFast
+ }
+ }
+ }
+
+ RowLayout {
+ spacing: Style.marginS
+ Layout.alignment: Qt.AlignRight
+
+ Repeater {
+ model: badgeLocations
+
+ delegate: NBox {
+ width: Style.baseWidgetSize * 0.7
+ height: Style.baseWidgetSize * 0.7
+ color: "transparent"
+ radius: Style.radiusS
+ border.width: 0
+
+ NText {
+ anchors.centerIn: parent
+ text: modelData
+ pointSize: Style.fontSizeXXS
+ font.weight: Style.fontWeightBold
+ color: highlighted ? Color.mOnTertiary : Color.mOnSurface
+ }
+ }
+ }
+ }
+ }
+ background: Rectangle {
+ width: listView.width
+ color: highlighted ? Color.mTertiary : Color.transparent
+ radius: Style.radiusS
+ Behavior on color {
+ ColorAnimation {
+ duration: Style.animationFast
+ }
}
}
}
@@ -234,8 +271,8 @@ RowLayout {
background: Rectangle {
color: Color.mSurfaceVariant
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
- radius: Style.radiusM * scaling
+ border.width: Math.max(1, Style.borderS)
+ radius: Style.radiusM
}
}
diff --git a/Modules/Settings/Bar/BarSectionEditor.qml b/Widgets/NSectionEditor.qml
similarity index 82%
rename from Modules/Settings/Bar/BarSectionEditor.qml
rename to Widgets/NSectionEditor.qml
index a36a1b17..b1dbefcd 100644
--- a/Modules/Settings/Bar/BarSectionEditor.qml
+++ b/Widgets/NSectionEditor.qml
@@ -13,8 +13,14 @@ NBox {
property string sectionId: ""
property var widgetModel: []
property var availableWidgets: []
+ property var availableSections: ["left", "center", "right"]
+ property int maxWidgets: -1 // -1 means unlimited
+
+ property var widgetRegistry: null
+ property string settingsDialogComponent: "BarWidgetSettingsDialog.qml"
readonly property real miniButtonSize: Style.baseWidgetSize * 0.65
+ readonly property bool isAtMaxCapacity: maxWidgets > 0 && widgetModel.length >= maxWidgets
signal addWidget(string widgetId, string section)
signal removeWidget(string section, int index)
@@ -27,16 +33,26 @@ NBox {
color: Color.mSurface
Layout.fillWidth: true
Layout.minimumHeight: {
- var widgetCount = widgetModel.length
- if (widgetCount === 0)
- return 140 * scaling
+ // header + minimal content area
+ var absoluteMin = (Style.marginL * 2) + (Style.fontSizeL * 2) + Style.marginM + (65 * Style.uiScaleRatio)
- var availableWidth = parent.width
- var avgWidgetWidth = 150 * scaling
+ var widgetCount = widgetModel.length
+ if (widgetCount === 0) {
+ return absoluteMin
+ }
+
+ // Calculate rows based on estimated widget layout
+ var availableWidth = parent.width - (Style.marginL * 2)
+ var avgWidgetWidth = 120 * Style.uiScaleRatio // More accurate estimate
var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth))
var rows = Math.ceil(widgetCount / widgetsPerRow)
- return (50 + 20 + (rows * 48) + ((rows - 1) * Style.marginS) + 20) * scaling
+ // Header height + spacing + (rows * widget height) + (spacing between rows) + margins
+ var headerHeight = Style.fontSizeL * 2
+ var widgetHeight = Style.baseWidgetSize * 1.15 * Style.uiScaleRatio
+ var widgetAreaHeight = ((rows + 1) * widgetHeight) + ((rows - 1) * Style.marginS)
+
+ return Math.max(absoluteMin, (Style.marginL * 2) + headerHeight + Style.marginM + widgetAreaHeight)
}
// Generate widget color from name checksum
@@ -62,23 +78,34 @@ NBox {
ColumnLayout {
anchors.fill: parent
- anchors.margins: Style.marginL * scaling
- spacing: Style.marginM * scaling
+ anchors.margins: Style.marginL
+ spacing: Style.marginM
RowLayout {
Layout.fillWidth: true
NText {
- text: sectionName + " Section"
- pointSize: Style.fontSizeL * scaling
+ text: sectionName
+ pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.alignment: Qt.AlignVCenter
}
+ // Widget count indicator (when max is set)
+ NText {
+ visible: root.maxWidgets > 0
+ text: "(" + widgetModel.length + "/" + root.maxWidgets + ")"
+ pointSize: Style.fontSizeS
+ color: root.isAtMaxCapacity ? Color.mError : Color.mOnSurfaceVariant
+ Layout.alignment: Qt.AlignVCenter
+ Layout.leftMargin: Style.marginXS
+ }
+
Item {
Layout.fillWidth: true
}
+
NSearchableComboBox {
id: comboBox
model: availableWidgets
@@ -87,8 +114,9 @@ NBox {
placeholder: I18n.tr("bar.widget-settings.section-editor.placeholder")
searchPlaceholder: I18n.tr("bar.widget-settings.section-editor.search-placeholder")
onSelected: key => comboBox.currentKey = key
- popupHeight: 340 * scaling
- minimumWidth: 200 * scaling
+ popupHeight: 300 * Style.uiScaleRatio
+ minimumWidth: 200 * Style.uiScaleRatio
+ enabled: !root.isAtMaxCapacity
Layout.alignment: Qt.AlignVCenter
@@ -110,12 +138,12 @@ NBox {
colorFg: Color.mOnPrimary
colorBgHover: Color.mSecondary
colorFgHover: Color.mOnSecondary
- enabled: comboBox.currentKey !== ""
- tooltipText: I18n.tr("tooltips.add-widget")
+ enabled: comboBox.currentKey !== "" && !root.isAtMaxCapacity
+ tooltipText: root.isAtMaxCapacity ? I18n.tr("tooltips.max-widgets-reached") : I18n.tr("tooltips.add-widget")
Layout.alignment: Qt.AlignVCenter
- Layout.leftMargin: Style.marginS * scaling
+ Layout.leftMargin: Style.marginS
onClicked: {
- if (comboBox.currentKey !== "") {
+ if (comboBox.currentKey !== "" && !root.isAtMaxCapacity) {
addWidget(comboBox.currentKey, sectionId)
comboBox.currentKey = ""
}
@@ -127,13 +155,13 @@ NBox {
Item {
Layout.fillWidth: true
Layout.fillHeight: true
- Layout.minimumHeight: 65 * scaling
+ Layout.minimumHeight: 65 * Style.uiScaleRatio
clip: false // Don't clip children so ghost can move freely
Flow {
id: widgetFlow
anchors.fill: parent
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
flow: Flow.LeftToRight
Repeater {
@@ -144,17 +172,17 @@ NBox {
required property int index
required property var modelData
- width: widgetContent.implicitWidth + Style.marginL * scaling
- height: Style.baseWidgetSize * 1.15 * scaling
- radius: Style.radiusL * scaling
+ width: widgetContent.implicitWidth + Style.marginL
+ height: Style.baseWidgetSize * 1.15 * Style.uiScaleRatio
+ radius: Style.radiusL
color: root.getWidgetColor(modelData)[0]
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
// Store the widget index for drag operations
property int widgetIndex: index
- readonly property int buttonsWidth: Math.round(20 * scaling)
- readonly property int buttonsCount: 1 + BarWidgetRegistry.widgetHasUserSettings(modelData.id)
+ readonly property int buttonsWidth: Math.round(20)
+ readonly property int buttonsCount: 1 + (root.widgetRegistry ? root.widgetRegistry.widgetHasUserSettings(modelData.id) : 0)
// Visual feedback during drag
opacity: flowDragArea.draggedIndex === index ? 0.5 : 1.0
@@ -176,30 +204,31 @@ NBox {
NContextMenu {
id: contextMenu
parent: Overlay.overlay
- width: 240 * scaling
+ width: 240 * Style.uiScaleRatio
model: [{
"label": I18n.tr("tooltips.move-to-left-section"),
"action": "left",
"icon": "arrow-bar-to-left",
- "visible": root.sectionId !== "left"
+ "visible": root.availableSections.includes("left") && root.sectionId !== "left"
}, {
"label": I18n.tr("tooltips.move-to-center-section"),
"action": "center",
"icon": "layout-columns",
- "visible": root.sectionId !== "center"
+ "visible": root.availableSections.includes("center") && root.sectionId !== "center"
}, {
"label": I18n.tr("tooltips.move-to-right-section"),
"action": "right",
"icon": "arrow-bar-to-right",
- "visible": root.sectionId !== "right"
+ "visible": root.availableSections.includes("right") && root.sectionId !== "right"
}]
onTriggered: action => root.moveWidget(root.sectionId, index, action)
}
- // Update the MouseArea to use the new context menu
+ // MouseArea for the context menu
MouseArea {
id: contextMouseArea
+ enabled: root.availableSections.length > 1 // Enable if there are other sections to move to
anchors.fill: parent
acceptedButtons: Qt.RightButton
z: -1 // Below the buttons but above background
@@ -209,9 +238,7 @@ NBox {
// Check if click is not on the buttons area
const localX = mouse.x
const buttonsStartX = parent.width - (parent.buttonsCount * parent.buttonsWidth)
-
if (localX < buttonsStartX) {
- // Use the helper function to open at mouse position
contextMenu.openAtItem(widgetItem, mouse.x, mouse.y)
}
}
@@ -220,23 +247,23 @@ NBox {
RowLayout {
id: widgetContent
anchors.centerIn: parent
- spacing: Style.marginXXS * scaling
+ spacing: Style.marginXXS
NText {
text: modelData.id
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeXS
color: root.getWidgetColor(modelData)[1]
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
- Layout.preferredWidth: 80 * scaling
+ Layout.preferredWidth: 60 * Style.uiScaleRatio
}
RowLayout {
spacing: 0
- Layout.preferredWidth: buttonsCount * buttonsWidth
+ Layout.preferredWidth: buttonsCount * buttonsWidth * Style.uiScaleRatio
Loader {
- active: BarWidgetRegistry.widgetHasUserSettings(modelData.id)
+ active: root.widgetRegistry && root.widgetRegistry.widgetHasUserSettings(modelData.id)
sourceComponent: NIconButton {
icon: "settings"
tooltipText: I18n.tr("tooltips.widget-settings")
@@ -247,7 +274,7 @@ NBox {
colorBgHover: Qt.alpha(Color.mOnPrimary, Style.opacityLight)
colorFgHover: Color.mOnPrimary
onClicked: {
- var component = Qt.createComponent(Qt.resolvedUrl("BarWidgetSettingsDialog.qml"))
+ var component = Qt.createComponent(Qt.resolvedUrl(root.settingsDialogComponent))
function instantiateAndOpen() {
var dialog = component.createObject(root, {
"widgetIndex": index,
@@ -258,19 +285,19 @@ NBox {
if (dialog) {
dialog.open()
} else {
- Logger.error("BarSectionEditor", "Failed to create settings dialog instance")
+ Logger.error("NSectionEditor", "Failed to create settings dialog instance")
}
}
if (component.status === Component.Ready) {
instantiateAndOpen()
} else if (component.status === Component.Error) {
- Logger.error("BarSectionEditor", component.errorString())
+ Logger.error("NSectionEditor", component.errorString())
} else {
component.statusChanged.connect(function () {
if (component.status === Component.Ready) {
instantiateAndOpen()
} else if (component.status === Component.Error) {
- Logger.error("BarSectionEditor", component.errorString())
+ Logger.error("NSectionEditor", component.errorString())
}
})
}
@@ -301,11 +328,11 @@ NBox {
Rectangle {
id: dragGhost
width: 0
- height: Style.baseWidgetSize * 1.15 * scaling
- radius: Style.radiusL * scaling
+ height: Style.baseWidgetSize * 1.15
+ radius: Style.radiusL
color: Color.transparent
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
opacity: 0.7
visible: flowDragArea.dragStarted
z: 2000
@@ -314,7 +341,7 @@ NBox {
NText {
id: ghostText
anchors.centerIn: parent
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
color: Color.mOnPrimary
}
}
@@ -322,8 +349,8 @@ NBox {
// Drop indicator - visual feedback for where the widget will be inserted
Rectangle {
id: dropIndicator
- width: 3 * scaling
- height: Style.baseWidgetSize * 1.15 * scaling
+ width: 3
+ height: Style.baseWidgetSize * 1.15
radius: width / 2
color: Color.mPrimary
opacity: 0
@@ -375,7 +402,7 @@ NBox {
property bool dragStarted: false
property bool potentialDrag: false // Track if we're in a potential drag interaction
property int draggedIndex: -1
- property real dragThreshold: 15 * scaling
+ property real dragThreshold: 15
property Item draggedWidget: null
property int dropTargetIndex: -1
property var draggedModelData: null
@@ -410,13 +437,13 @@ NBox {
if (leftDist < minDistance) {
minDistance = leftDist
bestIndex = i
- bestPosition = Qt.point(widget.x - dropIndicator.width / 2 - Style.marginXS * scaling, widget.y)
+ bestPosition = Qt.point(widget.x - dropIndicator.width / 2 - Style.marginXS, widget.y)
}
if (rightDist < minDistance) {
minDistance = rightDist
bestIndex = i + 1
- bestPosition = Qt.point(widget.x + widget.width + Style.marginXS * scaling - dropIndicator.width / 2, widget.y)
+ bestPosition = Qt.point(widget.x + widget.width + Style.marginXS - dropIndicator.width / 2, widget.y)
}
}
@@ -428,13 +455,13 @@ NBox {
if (dist < minDistance && mouseX < firstWidget.x + firstWidget.width / 2) {
minDistance = dist
bestIndex = 0
- bestPosition = Qt.point(Math.max(0, firstWidget.x - dropIndicator.width - Style.marginS * scaling), firstWidget.y)
+ bestPosition = Qt.point(Math.max(0, firstWidget.x - dropIndicator.width - Style.marginS), firstWidget.y)
}
}
}
// Only show indicator if we're close enough and it's a different position
- if (minDistance < 80 * scaling && bestIndex !== -1) {
+ if (minDistance < 80 && bestIndex !== -1) {
// Adjust index if we're moving forward
let adjustedIndex = bestIndex
if (bestIndex > draggedIndex) {
diff --git a/Widgets/NSlider.qml b/Widgets/NSlider.qml
index 77393099..fb9b0312 100644
--- a/Widgets/NSlider.qml
+++ b/Widgets/NSlider.qml
@@ -10,10 +10,13 @@ Slider {
property var cutoutColor: Color.mSurface
property bool snapAlways: true
property real heightRatio: 0.7
+ property string tooltipText
+ property string tooltipDirection: "auto"
+ property bool hovering: false
- readonly property real knobDiameter: Math.round((Style.baseWidgetSize * heightRatio * scaling) / 2) * 2
- readonly property real trackHeight: Math.round((knobDiameter * 0.4) / 2) * 2
- readonly property real cutoutExtra: Math.round((Style.baseWidgetSize * 0.1 * scaling) / 2) * 2
+ readonly property real knobDiameter: Math.round((Style.baseWidgetSize * heightRatio * Style.uiScaleRatio) / 2) * 2
+ readonly property real trackHeight: Math.round((knobDiameter * 0.4 * Style.uiScaleRatio) / 2) * 2
+ readonly property real cutoutExtra: Math.round((Style.baseWidgetSize * 0.1 * Style.uiScaleRatio) / 2) * 2
padding: cutoutExtra / 2
@@ -30,7 +33,7 @@ Slider {
radius: height / 2
color: Qt.alpha(Color.mSurface, 0.5)
border.color: Qt.alpha(Color.mOutline, 0.5)
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
// A container composite shape that puts a semicircle on the end
Item {
@@ -116,7 +119,7 @@ Slider {
radius: width / 2
color: root.pressed ? Color.mTertiary : Color.mSurface
border.color: Color.mPrimary
- border.width: Math.max(1, Style.borderL * scaling)
+ border.width: Math.max(1, Style.borderL)
anchors.centerIn: parent
Behavior on color {
@@ -125,5 +128,47 @@ Slider {
}
}
}
+
+ MouseArea {
+ enabled: true
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ hoverEnabled: true
+ // Pass through mouse events to the slider
+ propagateComposedEvents: true
+ preventStealing: false
+
+ onEntered: {
+ root.hovering = true
+ if (root.tooltipText) {
+ TooltipService.show(Screen, knob, root.tooltipText, root.tooltipDirection)
+ }
+ }
+
+ onExited: {
+ root.hovering = false
+ if (root.tooltipText) {
+ TooltipService.hide()
+ }
+ }
+
+ onPressed: function (mouse) {
+ if (root.tooltipText) {
+ TooltipService.hide()
+ }
+ // Pass the event through to the slider
+ mouse.accepted = false
+ }
+
+ onReleased: function (mouse) {
+ // Pass the event through to the slider
+ mouse.accepted = false
+ }
+
+ onPositionChanged: function (mouse) {
+ // Pass the event through to the slider
+ mouse.accepted = false
+ }
+ }
}
}
diff --git a/Widgets/NSpinBox.qml b/Widgets/NSpinBox.qml
index 8261135c..51310439 100644
--- a/Widgets/NSpinBox.qml
+++ b/Widgets/NSpinBox.qml
@@ -38,8 +38,8 @@ RowLayout {
// Main spinbox container
Rectangle {
id: spinBoxContainer
- implicitWidth: 120 * scaling
- implicitHeight: (root.baseSize - 4) * scaling
+ implicitWidth: 120
+ implicitHeight: (root.baseSize - 4)
radius: height * 0.5
color: Color.mSurfaceVariant
border.color: (root.hovering || decreaseArea.containsMouse || increaseArea.containsMouse) ? Color.mTertiary : Color.mOutline
@@ -153,7 +153,7 @@ RowLayout {
anchors.leftMargin: parent.width * 0.25
anchors.verticalCenter: parent.verticalCenter
icon: "chevron-left"
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
color: decreaseArea.containsMouse ? Color.mOnTertiary : Color.mPrimary
}
@@ -248,7 +248,7 @@ RowLayout {
anchors.rightMargin: parent.width * 0.25
anchors.verticalCenter: parent.verticalCenter
icon: "chevron-right"
- pointSize: Style.fontSizeS * scaling
+ pointSize: Style.fontSizeS
color: increaseArea.containsMouse ? Color.mOnTertiary : Color.mPrimary
}
@@ -271,7 +271,7 @@ RowLayout {
anchors.left: decreaseButton.right
anchors.right: increaseButton.left
anchors.verticalCenter: parent.verticalCenter
- anchors.margins: 4 * scaling
+ anchors.margins: 4
height: parent.height
color: Color.transparent
@@ -283,7 +283,7 @@ RowLayout {
NText {
text: root.prefix
family: Settings.data.ui.fontFixed
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
verticalAlignment: Text.AlignVCenter
@@ -296,7 +296,7 @@ RowLayout {
id: valueInput
text: valueInput.focus ? valueInput.text : root.value.toString()
font.family: Settings.data.ui.fontFixed
- font.pointSize: Style.fontSizeM * scaling
+ font.pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
verticalAlignment: Text.AlignVCenter
@@ -342,7 +342,7 @@ RowLayout {
NText {
text: root.suffix
family: Settings.data.ui.fontFixed
- pointSize: Style.fontSizeM * scaling
+ pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
verticalAlignment: Text.AlignVCenter
diff --git a/Widgets/NText.qml b/Widgets/NText.qml
index 9efd199e..4a9060ed 100644
--- a/Widgets/NText.qml
+++ b/Widgets/NText.qml
@@ -8,9 +8,14 @@ Text {
id: root
property string family: Settings.data.ui.fontDefault
- property real pointSize: Style.fontSizeM * scaling
+ property real pointSize: Style.fontSizeM
+ property bool applyUiScale: true
property real fontScale: {
- return (root.family === Settings.data.ui.fontDefault ? Settings.data.ui.fontDefaultScale : Settings.data.ui.fontFixedScale)
+ const fontScale = (root.family === Settings.data.ui.fontDefault ? Settings.data.ui.fontDefaultScale : Settings.data.ui.fontFixedScale)
+ if (applyUiScale) {
+ return fontScale * Style.uiScaleRatio
+ }
+ return fontScale
}
font.family: root.family
diff --git a/Widgets/NTextInput.qml b/Widgets/NTextInput.qml
index 7f2ec9d7..adb83cd3 100644
--- a/Widgets/NTextInput.qml
+++ b/Widgets/NTextInput.qml
@@ -14,7 +14,7 @@ ColumnLayout {
property color labelColor: Color.mOnSurface
property color descriptionColor: Color.mOnSurfaceVariant
property string fontFamily: Settings.data.ui.fontDefault
- property real fontSize: Style.fontSizeS * scaling
+ property real fontSize: Style.fontSizeS
property int fontWeight: Style.fontWeightRegular
property alias text: input.text
@@ -24,7 +24,7 @@ ColumnLayout {
signal editingFinished
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
NLabel {
label: root.label
@@ -40,8 +40,8 @@ ColumnLayout {
id: frameControl
Layout.fillWidth: true
- Layout.minimumWidth: 80 * scaling
- implicitHeight: Style.baseWidgetSize * 1.1 * scaling
+ Layout.minimumWidth: 80 * Style.uiScaleRatio
+ implicitHeight: Style.baseWidgetSize * 1.1 * Style.uiScaleRatio
// This is important - makes the control accept focus
focusPolicy: Qt.StrongFocus
@@ -50,10 +50,10 @@ ColumnLayout {
background: Rectangle {
id: frame
- radius: Style.radiusM * scaling
+ radius: Style.radiusM
color: Color.mSurface
border.color: input.activeFocus ? Color.mSecondary : Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
Behavior on border.color {
ColorAnimation {
@@ -79,7 +79,7 @@ ColumnLayout {
input.forceActiveFocus()
var inputPos = mapToItem(inputContainer, mouse.x, mouse.y)
if (inputPos.x >= 0 && inputPos.x <= inputContainer.width) {
- var textPos = inputPos.x - Style.marginM * scaling
+ var textPos = inputPos.x - Style.marginM
if (textPos >= 0 && textPos <= input.width) {
input.cursorPosition = input.positionAt(textPos, input.height / 2)
}
@@ -105,8 +105,8 @@ ColumnLayout {
Item {
id: inputContainer
anchors.fill: parent
- anchors.leftMargin: Style.marginM * scaling
- anchors.rightMargin: Style.marginM * scaling
+ anchors.leftMargin: Style.marginM
+ anchors.rightMargin: Style.marginM
z: 1
TextField {
@@ -131,7 +131,7 @@ ColumnLayout {
background: null
font.family: root.fontFamily
- font.pointSize: root.fontSize
+ font.pointSize: root.fontSize * Style.uiScaleRatio
font.weight: root.fontWeight
onEditingFinished: root.editingFinished()
diff --git a/Widgets/NTextInputButton.qml b/Widgets/NTextInputButton.qml
index 5e157c1e..89ec8ffd 100644
--- a/Widgets/NTextInputButton.qml
+++ b/Widgets/NTextInputButton.qml
@@ -21,7 +21,7 @@ ColumnLayout {
signal inputTextChanged(string text)
signal inputEditingFinished
- spacing: Style.marginS * scaling
+ spacing: Style.marginS
// Label and description
NLabel {
@@ -34,7 +34,7 @@ ColumnLayout {
// Input field with button
RowLayout {
Layout.fillWidth: true
- spacing: Style.marginM * scaling
+ spacing: Style.marginM
NTextInput {
id: input
diff --git a/Widgets/NToggle.qml b/Widgets/NToggle.qml
index 3e464bb8..18259ed6 100644
--- a/Widgets/NToggle.qml
+++ b/Widgets/NToggle.qml
@@ -11,7 +11,7 @@ RowLayout {
property string description: ""
property bool checked: false
property bool hovering: false
- property int baseSize: Math.round(Style.baseWidgetSize * 0.8)
+ property int baseSize: Math.round(Style.baseWidgetSize * 0.8 * Style.uiScaleRatio)
signal toggled(bool checked)
signal entered
@@ -27,12 +27,12 @@ RowLayout {
Rectangle {
id: switcher
- implicitWidth: Math.round(root.baseSize * .85 * scaling) * 2
- implicitHeight: Math.round(root.baseSize * scaling * .5) * 2
+ implicitWidth: Math.round(root.baseSize * .85) * 2
+ implicitHeight: Math.round(root.baseSize * .5) * 2
radius: height * 0.5
color: root.checked ? Color.mPrimary : Color.mSurface
border.color: Color.mOutline
- border.width: Math.max(1, Style.borderS * scaling)
+ border.width: Math.max(1, Style.borderS)
Behavior on color {
ColorAnimation {
@@ -48,15 +48,15 @@ RowLayout {
Rectangle {
- implicitWidth: Math.round(root.baseSize * 0.4 * scaling) * 2
- implicitHeight: Math.round(root.baseSize * 0.4 * scaling) * 2
+ implicitWidth: Math.round(root.baseSize * 0.4) * 2
+ implicitHeight: Math.round(root.baseSize * 0.4) * 2
radius: height * 0.5
color: root.checked ? Color.mOnPrimary : Color.mPrimary
border.color: root.checked ? Color.mSurface : Color.mSurface
- border.width: Math.max(1, Style.borderM * scaling)
+ border.width: Math.max(1, Style.borderM)
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 0
- x: root.checked ? switcher.width - width - 3 * scaling : 3 * scaling
+ x: root.checked ? switcher.width - width - 3 : 3
Behavior on x {
NumberAnimation {
diff --git a/Widgets/NValueSlider.qml b/Widgets/NValueSlider.qml
index 32d8ed76..9449d699 100644
--- a/Widgets/NValueSlider.qml
+++ b/Widgets/NValueSlider.qml
@@ -16,12 +16,16 @@ RowLayout {
property bool snapAlways: true
property real heightRatio: 0.7
property string text: ""
+ property real textSize: Style.fontSizeM
+ property real customHeight: -1
+ property real customHeightRatio: -1
// Signals
signal moved(real value)
signal pressedChanged(bool pressed, real value)
- spacing: Style.marginL * scaling
+ spacing: Style.marginL
+ implicitHeight: root.customHeight > 0 ? root.customHeight : slider.implicitHeight
NSlider {
id: slider
@@ -32,7 +36,7 @@ RowLayout {
stepSize: root.stepSize
cutoutColor: root.cutoutColor
snapAlways: root.snapAlways
- heightRatio: root.heightRatio
+ heightRatio: root.customHeightRatio > 0 ? root.customHeightRatio : root.heightRatio
onMoved: root.moved(value)
onPressedChanged: root.pressedChanged(pressed, value)
}
@@ -40,9 +44,10 @@ RowLayout {
NText {
visible: root.text !== ""
text: root.text
+ pointSize: root.textSize
family: Settings.data.ui.fontFixed
Layout.alignment: Qt.AlignVCenter
- Layout.preferredWidth: 45 * scaling
+ Layout.preferredWidth: 45 * Scale.uiScaleRatio
horizontalAlignment: Text.AlignRight
}
}
diff --git a/flake.nix b/flake.nix
index 90e83d63..54381f04 100644
--- a/flake.nix
+++ b/flake.nix
@@ -11,284 +11,47 @@
};
};
- outputs =
- {
- self,
- nixpkgs,
- systems,
- quickshell,
- ...
- }:
- let
- eachSystem = nixpkgs.lib.genAttrs (import systems);
- in
- {
- formatter = eachSystem (system: nixpkgs.legacyPackages.${system}.alejandra);
+ outputs = {
+ self,
+ nixpkgs,
+ systems,
+ quickshell,
+ ...
+ }: let
+ eachSystem = nixpkgs.lib.genAttrs (import systems);
+ in {
+ formatter = eachSystem (system: nixpkgs.legacyPackages.${system}.alejandra);
- packages = eachSystem (
- system:
- let
- pkgs = nixpkgs.legacyPackages.${system};
- qs = quickshell.packages.${system}.default.override {
+ packages = eachSystem (
+ system: let
+ pkgs = nixpkgs.legacyPackages.${system};
+ in {
+ default = pkgs.callPackage ./nix/package.nix {
+ version = self.rev or self.dirtyRev or "dirty";
+ quickshell = quickshell.packages.${system}.default.override {
withX11 = false;
- withI3 = false;
- };
-
- runtimeDeps =
- with pkgs;
- [
- bash
- bluez
- brightnessctl
- cava
- cliphist
- coreutils
- ddcutil
- file
- findutils
- libnotify
- matugen
- networkmanager
- wlsunset
- wl-clipboard
- ]
- ++ lib.optionals (pkgs.stdenv.hostPlatform.isx86_64) [ gpu-screen-recorder ];
-
- fontconfig = pkgs.makeFontsConf {
- fontDirectories = [
- pkgs.roboto
- pkgs.inter-nerdfont
- ];
- };
- in
- {
- default = pkgs.stdenv.mkDerivation {
- pname = "noctalia-shell";
- version = self.rev or self.dirtyRev or "dirty";
- src = ./.;
-
- nativeBuildInputs = [
- pkgs.gcc
- pkgs.makeWrapper
- pkgs.qt6.wrapQtAppsHook
- ];
- buildInputs = [
- qs
- pkgs.xkeyboard_config
- pkgs.qt6.qtbase
- ];
- propagatedBuildInputs = runtimeDeps;
-
- installPhase = ''
- mkdir -p $out/share/noctalia-shell
- cp -r ./* $out/share/noctalia-shell
-
- makeWrapper ${qs}/bin/qs $out/bin/noctalia-shell \
- --prefix PATH : "${pkgs.lib.makeBinPath runtimeDeps}" \
- --set FONTCONFIG_FILE "${fontconfig}" \
- --add-flags "-p $out/share/noctalia-shell"
- '';
-
- meta = {
- description = "A sleek and minimal desktop shell thoughtfully crafted for Wayland, built with Quickshell.";
- homepage = "https://github.com/noctalia-dev/noctalia-shell";
- license = pkgs.lib.licenses.mit;
- mainProgram = "noctalia-shell";
- };
- };
- }
- );
-
- defaultPackage = eachSystem (system: self.packages.${system}.default);
-
- homeModules.default =
- {
- config,
- lib,
- pkgs,
- ...
- }:
- let
- cfg = config.programs.noctalia-shell;
- defaultSettings = builtins.fromJSON (builtins.readFile ./Assets/settings-default.json);
-
- # Deep merge user settings with defaults
- mergedSettings =
- if cfg.settings == null then
- defaultSettings
- else if builtins.isAttrs cfg.settings then
- lib.recursiveUpdate defaultSettings cfg.settings
- else
- cfg.settings; # Pass through strings/paths as-is
- in
- {
- options.programs.noctalia-shell = {
- enable = lib.mkEnableOption "Noctalia shell configuration";
-
- settings = lib.mkOption {
- type =
- with lib.types;
- nullOr (oneOf [
- attrs
- str
- path
- ]);
- default = null;
- example = lib.literalExpression ''
- {
- bar = {
- position = "bottom";
- floating = true;
- backgroundOpacity = 0.95;
- };
- general = {
- animationSpeed = 1.5;
- radiusRatio = 1.2;
- };
- colorSchemes = {
- darkMode = true;
- useWallpaperColors = true;
- };
- }
- '';
- description = ''
- Noctalia shell configuration settings as an attribute set, string
- or filepath, to be written to ~/.config/noctalia/settings.json.
- When provided as an attribute set, it will be deep-merged with
- the default settings.
- '';
- };
-
- colors = lib.mkOption {
- type =
- with lib.types;
- nullOr (oneOf [
- attrs
- str
- path
- ]);
- default = null;
- example = lib.literalExpression ''
- {
- mError = "#dddddd";
- mOnError = "#111111";
- mOnPrimary = "#111111";
- mOnSecondary = "#111111";
- mOnSurface = "#828282";
- mOnSurfaceVariant = "#5d5d5d";
- mOnTertiary = "#111111";
- mOutline = "#3c3c3c";
- mPrimary = "#aaaaaa";
- mSecondary = "#a7a7a7";
- mShadow = "#000000";
- mSurface = "#111111";
- mSurfaceVariant = "#191919";
- mTertiary = "#cccccc";
- }
- '';
- description = ''
- Noctalia shell color configuration as an attribute set, string
- or filepath, to be written to ~/.config/noctalia/colors.json.
- '';
- };
- };
-
- config =
- let
- restart = ''
- ${pkgs.systemd}/bin/systemctl --user try-restart noctalia-shell.service 2>/dev/null || true
- '';
- in
- lib.mkIf cfg.enable {
- xdg.configFile = {
- "noctalia/settings.json" = {
- onChange = restart;
- }
- // (
- if builtins.isAttrs mergedSettings then
- { text = builtins.toJSON mergedSettings + "\n"; }
- else if builtins.isString mergedSettings then
- { text = mergedSettings; }
- else
- { source = mergedSettings; }
- );
- "noctalia/colors.json" = lib.mkIf (cfg.colors != null) (
- {
- onChange = restart;
- }
- // (
- if builtins.isAttrs cfg.colors then
- { text = builtins.toJSON cfg.colors; }
- else if builtins.isString cfg.colors then
- { text = cfg.colors; }
- else
- { source = cfg.colors; }
- )
- );
- };
- };
- };
-
- nixosModules.default =
- {
- config,
- lib,
- pkgs,
- ...
- }:
- let
- cfg = config.services.noctalia-shell;
- in
- {
- options.services.noctalia-shell = {
- enable = lib.mkEnableOption "Noctalia shell systemd service";
-
- package = lib.mkOption {
- type = lib.types.package;
- default = self.packages.${pkgs.system}.default;
- description = "The noctalia-shell package to use";
- };
-
- target = lib.mkOption {
- type = lib.types.str;
- default = "graphical-session.target";
- example = "hyprland-session.target";
- description = "The systemd target for the noctalia-shell service.";
- };
- };
-
- config = lib.mkIf cfg.enable {
- systemd.user.services.noctalia-shell = {
- description = "Noctalia Shell - Wayland desktop shell";
- documentation = [ "https://github.com/noctalia-dev/noctalia-shell" ];
- after = [ cfg.target ];
- partOf = [ cfg.target ];
- wantedBy = [ cfg.target ];
- restartTriggers = [ cfg.package ];
-
- environment = {
- PATH = lib.mkForce null;
- };
-
- unitConfig = {
- StartLimitIntervalSec = 60;
- StartLimitBurst = 3;
- };
-
- serviceConfig = {
- ExecStart = "${cfg.package}/bin/noctalia-shell";
- Restart = "on-failure";
- RestartSec = 3;
- TimeoutStartSec = 10;
- TimeoutStopSec = 5;
- Environment = [
- "NOCTALIA_SETTINGS_FALLBACK=%h/.config/noctalia/gui-settings.json"
- ];
- };
- };
-
- environment.systemPackages = [ cfg.package ];
+ withI3 = true;
};
};
+ }
+ );
+
+ defaultPackage = eachSystem (system: self.packages.${system}.default);
+
+ homeModules.default = {
+ pkgs,
+ lib,
+ ...
+ }: {
+ imports = [./nix/home-module.nix];
+ programs.noctalia-shell.app2unit.package =
+ lib.mkDefault
+ nixpkgs.legacyPackages.${pkgs.system}.app2unit;
};
+
+ nixosModules.default = {pkgs, ...}: {
+ imports = [./nix/nixos-module.nix];
+ services.noctalia-shell.package = self.packages.${pkgs.system}.default;
+ };
+ };
}
diff --git a/nix/home-module.nix b/nix/home-module.nix
new file mode 100644
index 00000000..08410911
--- /dev/null
+++ b/nix/home-module.nix
@@ -0,0 +1,114 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}: let
+ cfg = config.programs.noctalia-shell;
+ defaultSettings = builtins.fromJSON (builtins.readFile ../Assets/settings-default.json);
+ extractAttrs = x:
+ if builtins.isAttrs x
+ then x
+ else if builtins.isString x
+ then builtins.fromJson x
+ else builtins.fromJson (builtins.readFile x);
+in {
+ options.programs.noctalia-shell = {
+ enable = lib.mkEnableOption "Noctalia shell configuration";
+
+ settings = lib.mkOption {
+ type = with lib.types;
+ nullOr (oneOf [
+ attrs
+ str
+ path
+ ]);
+ default = {};
+ apply = x: lib.recursiveUpdate defaultSettings (extractAttrs x);
+ example = lib.literalExpression ''
+ {
+ bar = {
+ position = "bottom";
+ floating = true;
+ backgroundOpacity = 0.95;
+ };
+ general = {
+ animationSpeed = 1.5;
+ radiusRatio = 1.2;
+ };
+ colorSchemes = {
+ darkMode = true;
+ useWallpaperColors = true;
+ };
+ }
+ '';
+ description = ''
+ Noctalia shell configuration settings as an attribute set, string
+ or filepath, to be written to ~/.config/noctalia/settings.json.
+ When provided as an attribute set, it will be deep-merged with
+ the default settings.
+ '';
+ };
+
+ colors = lib.mkOption {
+ type = with lib.types;
+ nullOr (oneOf [
+ attrs
+ str
+ path
+ ]);
+ default = {};
+ apply = extractAttrs;
+ example = lib.literalExpression ''
+ {
+ mError = "#dddddd";
+ mOnError = "#111111";
+ mOnPrimary = "#111111";
+ mOnSecondary = "#111111";
+ mOnSurface = "#828282";
+ mOnSurfaceVariant = "#5d5d5d";
+ mOnTertiary = "#111111";
+ mOutline = "#3c3c3c";
+ mPrimary = "#aaaaaa";
+ mSecondary = "#a7a7a7";
+ mShadow = "#000000";
+ mSurface = "#111111";
+ mSurfaceVariant = "#191919";
+ mTertiary = "#cccccc";
+ }
+ '';
+ description = ''
+ Noctalia shell color configuration as an attribute set, string
+ or filepath, to be written to ~/.config/noctalia/colors.json.
+ '';
+ };
+
+ app2unit.package = lib.mkOption {
+ type = lib.types.package;
+ description = ''
+ The app2unit package to use when appLauncher.useApp2Unit is enabled.
+ '';
+ };
+ };
+
+ config = let
+ restart = ''
+ ${pkgs.systemd}/bin/systemctl --user try-restart noctalia-shell.service 2>/dev/null || true
+ '';
+ useApp2Unit = cfg.settings.appLauncher.useApp2Unit or false;
+ in
+ lib.mkIf cfg.enable {
+ home.packages = lib.optional useApp2Unit cfg.app2unit.package;
+
+ xdg.configFile = {
+ "noctalia/settings.json" = {
+ onChange = restart;
+ text = builtins.toJSON cfg.settings;
+ };
+ "noctalia/colors.json" = lib.mkIf (cfg.colors != {}) {
+ onChange = restart;
+ text = builtins.toJSON cfg.colors;
+ };
+ };
+ };
+}
diff --git a/nix/nixos-module.nix b/nix/nixos-module.nix
new file mode 100644
index 00000000..bbab247c
--- /dev/null
+++ b/nix/nixos-module.nix
@@ -0,0 +1,56 @@
+{
+ config,
+ lib,
+ ...
+}: let
+ cfg = config.services.noctalia-shell;
+in {
+ options.services.noctalia-shell = {
+ enable = lib.mkEnableOption "Noctalia shell systemd service";
+
+ package = lib.mkOption {
+ type = lib.types.package;
+ description = "The noctalia-shell package to use";
+ };
+
+ target = lib.mkOption {
+ type = lib.types.str;
+ default = "graphical-session.target";
+ example = "hyprland-session.target";
+ description = "The systemd target for the noctalia-shell service.";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ systemd.user.services.noctalia-shell = {
+ description = "Noctalia Shell - Wayland desktop shell";
+ documentation = ["https://github.com/noctalia-dev/noctalia-shell"];
+ after = [cfg.target];
+ partOf = [cfg.target];
+ wantedBy = [cfg.target];
+ restartTriggers = [cfg.package];
+
+ environment = {
+ PATH = lib.mkForce null;
+ };
+
+ unitConfig = {
+ StartLimitIntervalSec = 60;
+ StartLimitBurst = 3;
+ };
+
+ serviceConfig = {
+ ExecStart = "${cfg.package}/bin/noctalia-shell";
+ Restart = "on-failure";
+ RestartSec = 3;
+ TimeoutStartSec = 10;
+ TimeoutStopSec = 5;
+ Environment = [
+ "NOCTALIA_SETTINGS_FALLBACK=%h/.config/noctalia/gui-settings.json"
+ ];
+ };
+ };
+
+ environment.systemPackages = [cfg.package];
+ };
+}
diff --git a/nix/package.nix b/nix/package.nix
new file mode 100644
index 00000000..a5d07917
--- /dev/null
+++ b/nix/package.nix
@@ -0,0 +1,108 @@
+{
+ version ? "dirty",
+ lib,
+ stdenv,
+ # build
+ gcc,
+ qt6,
+ quickshell,
+ xkeyboard_config,
+ # runtime deps
+ bash,
+ bluez,
+ brightnessctl,
+ cava,
+ cliphist,
+ coreutils,
+ ddcutil,
+ file,
+ findutils,
+ libnotify,
+ matugen,
+ networkmanager,
+ wlsunset,
+ wl-clipboard,
+ gpu-screen-recorder, # optional
+ # fonts
+ makeFontsConf,
+ roboto,
+ inter-nerdfont,
+}: let
+ src = lib.cleanSourceWith {
+ src = ../.;
+ filter = path: type:
+ !(builtins.any (prefix: lib.path.hasPrefix (../. + prefix) (/. + path)) [
+ /.github
+ /Assets/Screenshots
+ /Assets/Wallpaper
+ /Bin/dev
+ /nix
+ /LICENSE
+ /README.md
+ /flake.nix
+ /flake.lock
+ ]);
+ };
+
+ runtimeDeps =
+ [
+ bash
+ bluez
+ brightnessctl
+ cava
+ cliphist
+ coreutils
+ ddcutil
+ file
+ findutils
+ libnotify
+ matugen
+ networkmanager
+ wlsunset
+ wl-clipboard
+ ]
+ ++ lib.optionals (stdenv.hostPlatform.isx86_64) [gpu-screen-recorder];
+
+ fontconfig = makeFontsConf {
+ fontDirectories = [
+ roboto
+ inter-nerdfont
+ ];
+ };
+in
+ stdenv.mkDerivation {
+ pname = "noctalia-shell";
+ inherit version src;
+
+ nativeBuildInputs = [
+ gcc
+ qt6.wrapQtAppsHook
+ ];
+ buildInputs = [
+ quickshell
+ xkeyboard_config
+ qt6.qtbase
+ ];
+ propagatedBuildInputs = runtimeDeps;
+
+ installPhase = ''
+ mkdir -p $out/share/noctalia-shell $out/bin
+ cp -r ./* $out/share/noctalia-shell
+ cp ${quickshell}/bin/qs $out/bin/noctalia-shell
+ '';
+
+ preFixup = ''
+ qtWrapperArgs+=(
+ --prefix PATH : ${lib.makeBinPath runtimeDeps}
+ --set FONTCONFIG_FILE ${fontconfig}
+ --add-flags "-p $out/share/noctalia-shell"
+ )
+ '';
+
+ meta = {
+ description = "A sleek and minimal desktop shell thoughtfully crafted for Wayland, built with Quickshell.";
+ homepage = "https://github.com/noctalia-dev/noctalia-shell";
+ license = lib.licenses.mit;
+ mainProgram = "noctalia-shell";
+ };
+ }
diff --git a/shell.qml b/shell.qml
index ec530610..39b76224 100644
--- a/shell.qml
+++ b/shell.qml
@@ -78,9 +78,6 @@ ShellRoot {
sourceComponent: Item {
Component.onCompleted: {
- // Save a ref. to our lockScreen so we can access it easily
- PanelService.lockScreen = lockScreen
-
Logger.log("Shell", "---------------------------")
WallpaperService.init()
AppThemeService.init()
@@ -106,6 +103,10 @@ ShellRoot {
LockScreen {
id: lockScreen
+ Component.onCompleted: {
+ // Save a ref. to our lockScreen so we can access it easily
+ PanelService.lockScreen = lockScreen
+ }
}
ToastOverlay {}