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 {}