Compare commits
229 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
899595ec5c | ||
|
|
dbf1020636 | ||
|
|
7def695c0e | ||
|
|
b51a87a981 | ||
|
|
3da2682111 | ||
|
|
758f2f2e55 | ||
|
|
26a27c3393 | ||
|
|
9d9bfb54e1 | ||
|
|
ab5b1e4d82 | ||
|
|
8cb9f04a22 | ||
|
|
22bc5a3bff | ||
|
|
f5982f41a2 | ||
|
|
9a80d51b10 | ||
|
|
fa838ecdb1 | ||
|
|
c0d6780c3d | ||
|
|
8935f9a0f9 | ||
|
|
b8b97c46a0 | ||
|
|
f2bbf70f93 | ||
|
|
ecf468f78f | ||
|
|
0b35fc1d2d | ||
|
|
94c5d73a61 | ||
|
|
f399a6d9f5 | ||
|
|
44fd859aec | ||
|
|
53d0c3943d | ||
|
|
d80f923802 | ||
|
|
1b861d7b7b | ||
|
|
65933208ec | ||
|
|
5df218a789 | ||
|
|
2f7a834b55 | ||
|
|
eca301553e | ||
|
|
91efa38101 | ||
|
|
acfe94f736 | ||
|
|
97bfcbb9e8 | ||
|
|
9e47d91be2 | ||
|
|
8872002225 | ||
|
|
519a85b251 | ||
|
|
5aa7ff7e91 | ||
|
|
5ce5659b38 | ||
|
|
00459606ce | ||
|
|
da1081700a | ||
|
|
7e965262f5 | ||
|
|
19312d94c3 | ||
|
|
118323e6b5 | ||
|
|
aed81e82b0 | ||
|
|
76c167c2c2 | ||
|
|
852e2fa4d1 | ||
|
|
17dceffff6 | ||
|
|
b589f37e0b | ||
|
|
682c6af231 | ||
|
|
d71226c6bd | ||
|
|
1f3725faf8 | ||
|
|
efcec1b2f9 | ||
|
|
651322aef0 | ||
|
|
a0a3a58668 | ||
|
|
b3abe44d65 | ||
|
|
5b603472bd | ||
|
|
57b0fe8a21 | ||
|
|
f5561da3cc | ||
|
|
11f6475b9f | ||
|
|
fb2c5e0470 | ||
|
|
b1764fddc8 | ||
|
|
bb7f957e44 | ||
|
|
0682315c9d | ||
|
|
dd100597ed | ||
|
|
6bc6380ee1 | ||
|
|
966089e471 | ||
|
|
3956461254 | ||
|
|
02d114a05e | ||
|
|
3764edafa8 | ||
|
|
1cd0376381 | ||
|
|
2b154e2cdb | ||
|
|
933dfc402b | ||
|
|
34d037d7dc | ||
|
|
76b6626073 | ||
|
|
d348cfc2b0 | ||
|
|
f9d7de2e3c | ||
|
|
2ea00fffa5 | ||
|
|
b163dab241 | ||
|
|
af0f4818d8 | ||
|
|
8b6c7632af | ||
|
|
d6d51d24c9 | ||
|
|
0c6aea7154 | ||
|
|
a61b2edd07 | ||
|
|
c108e7707a | ||
|
|
f3123ba5b1 | ||
|
|
c09a93af48 | ||
|
|
2a262999ce | ||
|
|
7d952dc226 | ||
|
|
3cb838b455 | ||
|
|
7594651e05 | ||
|
|
0d611fc891 | ||
|
|
8982909fae | ||
|
|
132b331c7c | ||
|
|
85cef214c8 | ||
|
|
80b4dad199 | ||
|
|
aadbc9596d | ||
|
|
0949d154c1 | ||
|
|
a86a0d33c1 | ||
|
|
e6372a2473 | ||
|
|
e3d9ab5679 | ||
|
|
ccd7458ea3 | ||
|
|
d41b59d563 | ||
|
|
290ba4ac03 | ||
|
|
1ee14df915 | ||
|
|
76376a9783 | ||
|
|
880ac93662 | ||
|
|
1157c8e21d | ||
|
|
2082cfe7c7 | ||
|
|
9a9f2886e0 | ||
|
|
0035fbcc4e | ||
|
|
5ca2c2a095 | ||
|
|
46103062d0 | ||
|
|
78a41c236c | ||
|
|
9dfac69e9e | ||
|
|
de72236fe5 | ||
|
|
101e3125a9 | ||
|
|
b443c9f492 | ||
|
|
2a1e7832d6 | ||
|
|
8c815146e6 | ||
|
|
acae2b8c21 | ||
|
|
004836fc8f | ||
|
|
b51f2d16cb | ||
|
|
6fba9d9f22 | ||
|
|
335e38d461 | ||
|
|
ee50d84a53 | ||
|
|
e706dabef3 | ||
|
|
dcedae46e5 | ||
|
|
f27f9d35b0 | ||
|
|
4f5acb7114 | ||
|
|
25ba27cbdd | ||
|
|
74da975ed4 | ||
|
|
6f6a5b364a | ||
|
|
f670f88804 | ||
|
|
814cb774a6 | ||
|
|
50d8b54adf | ||
|
|
ae931b791f | ||
|
|
dd4641eedd | ||
|
|
b66bb46fc1 | ||
|
|
5079fc78d3 | ||
|
|
7d2eaa46e6 | ||
|
|
1043eaa39f | ||
|
|
f16798f6e3 | ||
|
|
96acb1a679 | ||
|
|
0f93797ab3 | ||
|
|
3186a84d6b | ||
|
|
d3ee66d845 | ||
|
|
a8837283ab | ||
|
|
6fe0784c00 | ||
|
|
59ce164b40 | ||
|
|
70144eb06f | ||
|
|
5136af5d95 | ||
|
|
ff42244c6d | ||
|
|
3a2bb40117 | ||
|
|
bcd3100849 | ||
|
|
c8886629ad | ||
|
|
be4a69f6e0 | ||
|
|
62b12d5436 | ||
|
|
99e75d51b8 | ||
|
|
079c8f0803 | ||
|
|
16f87cbfa3 | ||
|
|
380f31fbd9 | ||
|
|
28677d6888 | ||
|
|
b9ae772987 | ||
|
|
4265290a0f | ||
|
|
07e94b0f0e | ||
|
|
307318918d | ||
|
|
3f6662182e | ||
|
|
be532fa146 | ||
|
|
722a59da80 | ||
|
|
3c97acf00f | ||
|
|
2d4fa59c41 | ||
|
|
c5ca758d3e | ||
|
|
6f70a98b83 | ||
|
|
424594a11a | ||
|
|
f5ac42c692 | ||
|
|
675f96d0e6 | ||
|
|
9570688294 | ||
|
|
626b745ce3 | ||
|
|
4afb98cf4c | ||
|
|
df2a9a246d | ||
|
|
d80e9ba3d5 | ||
|
|
130c68b3e2 | ||
|
|
6eea4a17a4 | ||
|
|
40dc8633ec | ||
|
|
12ac91d125 | ||
|
|
87d86911d7 | ||
|
|
2872a7b5c9 | ||
|
|
4067896434 | ||
|
|
78443451e4 | ||
|
|
719f5a20e7 | ||
|
|
d8b12e6d6b | ||
|
|
9a0746d737 | ||
|
|
77f8b3937c | ||
|
|
3f4313635a | ||
|
|
004d92a85d | ||
|
|
720c17258b | ||
|
|
a8b312f3a7 | ||
|
|
4d6361dfe5 | ||
|
|
1f75819795 | ||
|
|
50ddd2916c | ||
|
|
d30e14f611 | ||
|
|
227b0dd962 | ||
|
|
ac61086c95 | ||
|
|
0980f65751 | ||
|
|
7aa3da2ff4 | ||
|
|
83fbb8f95d | ||
|
|
a029463527 | ||
|
|
baafe54d13 | ||
|
|
a1cbd35202 | ||
|
|
1337a35a1e | ||
|
|
61006fbed0 | ||
|
|
eff4337d35 | ||
|
|
f0733f19dd | ||
|
|
818df48787 | ||
|
|
0eedfba071 | ||
|
|
2dc9e2f212 | ||
|
|
62a3b343cf | ||
|
|
76be93a84d | ||
|
|
b59c56170e | ||
|
|
c9285d8c5b | ||
|
|
b157d855a8 | ||
|
|
82ac49ce85 | ||
|
|
7247a26586 | ||
|
|
be0b568f1f | ||
|
|
e4b54e518c | ||
|
|
6ea1e2b4c7 | ||
|
|
5b4c57eae2 | ||
|
|
4afe2d8448 | ||
|
|
44cdbfe5d7 |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug from noctalia-shell
|
||||
title: "[Bug]: "
|
||||
title: "[Bug] "
|
||||
labels: bug
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest a new feature or improvement
|
||||
title: "[Feature]: "
|
||||
title: "[Feature] "
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.qmlls.ini
|
||||
@@ -4,7 +4,7 @@
|
||||
"mOnPrimary": "#11111b",
|
||||
"mSecondary": "#fab387",
|
||||
"mOnSecondary": "#11111b",
|
||||
"mTertiary": "#a6e3a1",
|
||||
"mTertiary": "#94e2d5",
|
||||
"mOnTertiary": "#11111b",
|
||||
"mError": "#f38ba8",
|
||||
"mOnError": "#11111b",
|
||||
@@ -16,19 +16,19 @@
|
||||
"mShadow": "#11111b"
|
||||
},
|
||||
"light": {
|
||||
"mPrimary": "#9349ef",
|
||||
"mPrimary": "#8839ef",
|
||||
"mOnPrimary": "#eff1f5",
|
||||
"mSecondary": "#f67525",
|
||||
"mSecondary": "#fe640b",
|
||||
"mOnSecondary": "#eff1f5",
|
||||
"mTertiary": "#40b635",
|
||||
"mTertiary": "#40a02b",
|
||||
"mOnTertiary": "#eff1f5",
|
||||
"mError": "#f38ba8",
|
||||
"mOnError": "#11111b",
|
||||
"mError": "#d20f39",
|
||||
"mOnError": "#dce0e8",
|
||||
"mSurface": "#eff1f5",
|
||||
"mOnSurface": "#4c4f69",
|
||||
"mSurfaceVariant": "#ccd0da",
|
||||
"mOnSurfaceVariant": "#6c6f85",
|
||||
"mOutline": "#aeb5c4",
|
||||
"mOutline": "#a5adcb",
|
||||
"mShadow": "#dce0e8"
|
||||
}
|
||||
}
|
||||
|
||||
34
Assets/ColorScheme/Everforest.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"dark": {
|
||||
"mPrimary": "#D3C6AA",
|
||||
"mOnPrimary": "#232A2E",
|
||||
"mSecondary": "#D3C6AA",
|
||||
"mOnSecondary": "#232A2E",
|
||||
"mTertiary": "#9DA9A0",
|
||||
"mOnTertiary": "#232A2E",
|
||||
"mError": "#E67E80",
|
||||
"mOnError": "#232A2E",
|
||||
"mSurface": "#232A2E",
|
||||
"mOnSurface": "#859289",
|
||||
"mSurfaceVariant": "#2D353B",
|
||||
"mOnSurfaceVariant": "#D3C6AA",
|
||||
"mOutline": "#D3C6AA",
|
||||
"mShadow": "#475258"
|
||||
},
|
||||
"light": {
|
||||
"mPrimary": "#434F55",
|
||||
"mOnPrimary": "#D3C6AA",
|
||||
"mSecondary": "#232a2e",
|
||||
"mOnSecondary": "#D3C6AA",
|
||||
"mTertiary": "#333c43",
|
||||
"mOnTertiary": "#9DA9A0",
|
||||
"mError": "#E66868",
|
||||
"mOnError": "#9DA9A0",
|
||||
"mSurface": "#BEC5B2",
|
||||
"mOnSurface": "#333C43",
|
||||
"mSurfaceVariant": "#9DA9A0",
|
||||
"mOnSurfaceVariant": "#232A2E",
|
||||
"mOutline": "#232A2E",
|
||||
"mShadow": "#ECF5ED"
|
||||
}
|
||||
}
|
||||
34
Assets/ColorScheme/Monochrome.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"dark": {
|
||||
"mPrimary": "#aaaaaa",
|
||||
"mOnPrimary": "#111111",
|
||||
"mSecondary": "#a7a7a7",
|
||||
"mOnSecondary": "#111111",
|
||||
"mTertiary": "#cccccc",
|
||||
"mOnTertiary": "#111111",
|
||||
"mError": "#dddddd",
|
||||
"mOnError": "#111111",
|
||||
"mSurface": "#111111",
|
||||
"mOnSurface": "#828282",
|
||||
"mSurfaceVariant": "#191919",
|
||||
"mOnSurfaceVariant": "#5d5d5d",
|
||||
"mOutline": "#3c3c3c",
|
||||
"mShadow": "#000000"
|
||||
},
|
||||
"light": {
|
||||
"mPrimary": "#555555",
|
||||
"mOnPrimary": "#eeeeee",
|
||||
"mSecondary": "#505058",
|
||||
"mOnSecondary": "#eeeeee",
|
||||
"mTertiary": "#333333",
|
||||
"mOnTertiary": "#eeeeee",
|
||||
"mError": "#222222",
|
||||
"mOnError": "#efefef",
|
||||
"mSurface": "#d4d4d4",
|
||||
"mOnSurface": "#696969",
|
||||
"mSurfaceVariant": "#e8e8e8",
|
||||
"mOnSurfaceVariant": "#9e9e9e",
|
||||
"mOutline": "#c3c3c3",
|
||||
"mShadow": "#fafafa"
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
"mOnPrimary": "#191724",
|
||||
"mSecondary": "#9ccfd8",
|
||||
"mOnSecondary": "#191724",
|
||||
"mTertiary": "#f6c177",
|
||||
"mOnTertiary": "#191724",
|
||||
"mTertiary": "#524f67",
|
||||
"mOnTertiary": "#e0def4",
|
||||
"mError": "#eb6f92",
|
||||
"mOnError": "#191724",
|
||||
"mSurface": "#191724",
|
||||
@@ -16,19 +16,19 @@
|
||||
"mShadow": "#191724"
|
||||
},
|
||||
"light": {
|
||||
"mPrimary": "#d46e6b",
|
||||
"mPrimary": "#d7827e",
|
||||
"mOnPrimary": "#faf4ed",
|
||||
"mSecondary": "#56949f",
|
||||
"mOnSecondary": "#faf4ed",
|
||||
"mTertiary": "#31748f",
|
||||
"mOnTertiary": "#232136",
|
||||
"mTertiary": "#cecacd",
|
||||
"mOnTertiary": "#575279",
|
||||
"mError": "#b4637a",
|
||||
"mOnError": "#f2e9e1",
|
||||
"mSurface": "#e0def4",
|
||||
"mOnSurface": "#232136",
|
||||
"mSurfaceVariant": "#bcb8e7",
|
||||
"mOnError": "#faf4ed",
|
||||
"mSurface": "#faf4ed",
|
||||
"mOnSurface": "#575279",
|
||||
"mSurfaceVariant": "#f2e9e1",
|
||||
"mOnSurfaceVariant": "#797593",
|
||||
"mOutline": "#9893a5",
|
||||
"mShadow": "#575279"
|
||||
"mOutline": "#dfdad9",
|
||||
"mShadow": "#faf4ed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
{
|
||||
"dark": {
|
||||
"mPrimary": "#ff9e64",
|
||||
"mOnPrimary": "#1a1b26",
|
||||
"mSecondary": "#e0af68",
|
||||
"mOnSecondary": "#1a1b26",
|
||||
"mTertiary": "#7aa2f7",
|
||||
"mOnTertiary": "#1a1b26",
|
||||
"mPrimary": "#7aa2f7",
|
||||
"mOnPrimary": "#16161e",
|
||||
"mSecondary": "#bb9af7",
|
||||
"mOnSecondary": "#16161e",
|
||||
"mTertiary": "#9ece6a",
|
||||
"mOnTertiary": "#16161e",
|
||||
"mError": "#f7768e",
|
||||
"mOnError": "#1a1b26",
|
||||
"mOnError": "#16161e",
|
||||
"mSurface": "#1a1b26",
|
||||
"mOnSurface": "#a9b1d6",
|
||||
"mSurfaceVariant": "#292e42",
|
||||
"mOnSurfaceVariant": "#787c99",
|
||||
"mOutline": "#3d4462",
|
||||
"mShadow": "#1a1b26"
|
||||
"mOnSurface": "#c0caf5",
|
||||
"mSurfaceVariant": "#24283b",
|
||||
"mOnSurfaceVariant": "#9aa5ce",
|
||||
"mOutline": "#565f89",
|
||||
"mShadow": "#15161e"
|
||||
},
|
||||
"light": {
|
||||
"mPrimary": "#fd5d00",
|
||||
"mOnPrimary": "#e6e7ed",
|
||||
"mSecondary": "#bb8027",
|
||||
"mOnSecondary": "#e6e7ed",
|
||||
"mTertiary": "#4a80f4",
|
||||
"mOnTertiary": "#e6e7ed",
|
||||
"mError": "#965027",
|
||||
"mOnError": "#e6e7ed",
|
||||
"mSurface": "#e6e7ed",
|
||||
"mOnSurface": "#343b58",
|
||||
"mSurfaceVariant": "#d5d6db",
|
||||
"mOnSurfaceVariant": "#40434f",
|
||||
"mOutline": "#babbc3",
|
||||
"mShadow": "#c0caf5"
|
||||
"mPrimary": "#2e7de9",
|
||||
"mOnPrimary": "#e1e2e7",
|
||||
"mSecondary": "#9854f1",
|
||||
"mOnSecondary": "#e1e2e7",
|
||||
"mTertiary": "#587539",
|
||||
"mOnTertiary": "#e1e2e7",
|
||||
"mError": "#f52a65",
|
||||
"mOnError": "#e1e2e7",
|
||||
"mSurface": "#e1e2e7",
|
||||
"mOnSurface": "#3760bf",
|
||||
"mSurfaceVariant": "#d0d5e3",
|
||||
"mOnSurfaceVariant": "#6172b0",
|
||||
"mOutline": "#b4b5b9",
|
||||
"mShadow": "#a8aecb"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
Assets/Fonts/tabler/tabler-icons.ttf
Normal file
@@ -51,28 +51,31 @@ Singleton {
|
||||
lines.push("\n[templates.ghostty]")
|
||||
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/ghostty.conf"')
|
||||
lines.push('output_path = "~/.config/ghostty/themes/noctalia"')
|
||||
lines.push(
|
||||
"post_hook = \"grep -q '^theme *= *' ~/.config/ghostty/config; and sed -i 's/^theme *= *.*/theme = noctalia/' ~/.config/ghostty/config; or echo 'theme = noctalia' >> ~/.config/ghostty/config\"")
|
||||
lines.push("post_hook = \"grep -q '^theme *= *' ~/.config/ghostty/config; and sed -i 's/^theme *= *.*/theme = noctalia/' ~/.config/ghostty/config; or echo 'theme = noctalia' >> ~/.config/ghostty/config\"")
|
||||
}
|
||||
if (Settings.data.matugen.foot) {
|
||||
lines.push("\n[templates.foot]")
|
||||
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/foot.conf"')
|
||||
lines.push('output_path = "~/.config/foot/themes/noctalia"')
|
||||
lines.push(
|
||||
'post_hook = "sed -i /themes/d ~/.config/foot/foot.ini && echo include=~/.config/foot/themes/noctalia >> ~/.config/foot/foot.ini"')
|
||||
lines.push('post_hook = "sed -i /themes/d ~/.config/foot/foot.ini && echo include=~/.config/foot/themes/noctalia >> ~/.config/foot/foot.ini"')
|
||||
}
|
||||
if (Settings.data.matugen.fuzzel) {
|
||||
lines.push("\n[templates.fuzzel]")
|
||||
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/fuzzel.conf"')
|
||||
lines.push('output_path = "~/.config/fuzzel/themes/noctalia"')
|
||||
lines.push(
|
||||
'post_hook = "sed -i /themes/d ~/.config/fuzzel/fuzzel.ini && echo include=~/.config/fuzzel/themes/noctalia >> ~/.config/fuzzel/fuzzel.ini"')
|
||||
lines.push('post_hook = "sed -i /themes/d ~/.config/fuzzel/fuzzel.ini && echo include=~/.config/fuzzel/themes/noctalia >> ~/.config/fuzzel/fuzzel.ini"')
|
||||
}
|
||||
if (Settings.data.matugen.vesktop) {
|
||||
lines.push("\n[templates.vesktop]")
|
||||
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/vesktop.css"')
|
||||
lines.push('output_path = "~/.config/vesktop/themes/noctalia.theme.css"')
|
||||
}
|
||||
if (Settings.data.matugen.pywalfox) {
|
||||
lines.push("\n[templates.pywalfox]")
|
||||
lines.push('input_path = "' + Quickshell.shellDir + '/Assets/Matugen/templates/pywalfox.json"')
|
||||
lines.push('output_path = "~/.cache/wal/colors.json"')
|
||||
lines.push('post_hook = "pywalfox update"')
|
||||
}
|
||||
|
||||
return lines.join("\n") + "\n"
|
||||
}
|
||||
|
||||
22
Assets/Matugen/templates/pywalfox.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"wallpaper": "{{image}}",
|
||||
"alpha": "100",
|
||||
"colors": {
|
||||
"color0": "{{colors.background.default.hex}}",
|
||||
"color1": "",
|
||||
"color2": "",
|
||||
"color3": "",
|
||||
"color4": "",
|
||||
"color5": "",
|
||||
"color6": "",
|
||||
"color7": "",
|
||||
"color8": "",
|
||||
"color9": "",
|
||||
"color10": "{{colors.primary.default.hex}}",
|
||||
"color11": "",
|
||||
"color12": "",
|
||||
"color13": "{{colors.surface_bright.default.hex}}",
|
||||
"color14": "",
|
||||
"color15": "{{colors.on_surface.default.hex}}"
|
||||
}
|
||||
}
|
||||
@@ -1,572 +1,113 @@
|
||||
/*
|
||||
* Vesktop Theme
|
||||
* Generated with Matugen
|
||||
* Base was taken from https://github.com/catppuccin/discord <3
|
||||
/**
|
||||
* @name noctalia
|
||||
* @description Original theme: midnight | A dark, rounded discord theme.
|
||||
* @author refact0r
|
||||
* @version 1.6.2
|
||||
* @invite nz87hXyvcy
|
||||
* @website https://github.com/refact0r/midnight-discord
|
||||
* @source https://github.com/refact0r/midnight-discord/blob/master/midnight.theme.css
|
||||
* @authorId 508863359777505290
|
||||
* @authorLink https://www.refact0r.dev
|
||||
*/
|
||||
|
||||
/* IMPORTANT: make sure to enable dark mode in discord settings for the theme to apply properly!!! */
|
||||
|
||||
/* Dark Theme */
|
||||
.visual-refresh.theme-dark,
|
||||
.visual-refresh .theme-dark {
|
||||
/* Brand Colors */
|
||||
--brand-experiment: {{colors.primary.default.hex}};
|
||||
--bg-brand: {{colors.primary.default.hex}};
|
||||
--brand-500: {{colors.primary.default.hex}} !important;
|
||||
--text-link: {{colors.primary.default.hex}} !important;
|
||||
--text-brand: {{colors.primary.default.hex}};
|
||||
--control-brand-foreground: {{colors.primary.default.hex}};
|
||||
--control-brand-foreground-new: {{colors.primary.default.hex}};
|
||||
--mention-foreground: {{colors.primary.default.hex}};
|
||||
--mention-background: {{colors.primary.default.hex}}20;
|
||||
--focus-primary: {{colors.primary.default.hex}};
|
||||
--logo-primary: {{colors.on_surface.default.hex}};
|
||||
--badge-brand-bg: {{colors.primary.default.hex}};
|
||||
--badge-brand-text: {{colors.on_primary.default.hex}};
|
||||
@import url('https://refact0r.github.io/midnight-discord/build/midnight.css');
|
||||
|
||||
/* Text Colors */
|
||||
--header-primary: {{colors.on_surface.default.hex}} !important;
|
||||
--header-secondary: {{colors.on_surface_variant.default.hex}} !important;
|
||||
--text-normal: {{colors.on_surface.default.hex}} !important;
|
||||
--text-default: {{colors.on_surface.default.hex}};
|
||||
--text-muted: {{colors.on_surface_variant.default.hex}} !important;
|
||||
--text-primary: {{colors.on_surface.default.hex}};
|
||||
--text-secondary: {{colors.on_surface_variant.default.hex}};
|
||||
--text-tertiary: {{colors.on_surface_variant.default.hex}} !important;
|
||||
--interactive-normal: {{colors.on_surface.default.hex}} !important;
|
||||
--interactive-muted: {{colors.on_surface_variant.default.hex}};
|
||||
--interactive-hover: {{colors.on_surface.default.hex}};
|
||||
--interactive-active: {{colors.on_surface.default.hex}};
|
||||
/* customize things here */
|
||||
:root {
|
||||
/* font, change to 'gg sans' for default discord font*/
|
||||
--font: 'figtree';
|
||||
|
||||
/* Main Background Colors - Bar color (mSurface) colors.surface.default.hex*/
|
||||
--background-primary: {{colors.surface_variant.default.hex}} !important;
|
||||
--background-floating: {{colors.surface_variant.default.hex}} !important;
|
||||
--background-surface-high: {{colors.surface_variant.default.hex}} !important;
|
||||
--modal-background: {{colors.surface_variant.default.hex}} !important;
|
||||
--app-background-frame: {{colors.surface_variant.default.hex}} !important;
|
||||
--home-background: {{colors.surface_variant.default.hex}} !important;
|
||||
--chat-background: {{colors.surface_variant.default.hex}} !important;
|
||||
--chat-background-default: {{colors.surface_variant.default.hex}} !important;
|
||||
--chat-input-container-background: {{colors.surface_container.default.hex}} !important;
|
||||
|
||||
/* Secondary Background Colors - Workspace color (mSurfaceVariant) */
|
||||
--background-secondary: {{colors.surface.default.hex}} !important;
|
||||
--background-secondary-alt: {{colors.surface.default.hex}} !important;
|
||||
--background-surface-higher: {{colors.surface.default.hex}} !important;
|
||||
--background-base-low: {{colors.surface.default.hex}} !important;
|
||||
--background-base-lower: {{colors.surface.default.hex}} !important;
|
||||
--channeltextarea-background: {{colors.surface_container.default.hex}} !important;
|
||||
--modal-footer-background: {{colors.surface.default.hex}} !important;
|
||||
|
||||
/* New Messages Banner */
|
||||
--background-mentioned: {{colors.primary.default.hex}}15 !important;
|
||||
--background-mentioned-hover: {{colors.primary.default.hex}}20 !important;
|
||||
--text-mentioned: {{colors.on_surface.default.hex}} !important;
|
||||
--text-mentioned-hover: {{colors.on_surface.default.hex}} !important;
|
||||
--text-mentioned-link: {{colors.primary.default.hex}} !important;
|
||||
|
||||
/* Additional Discord-specific variables for new messages banner */
|
||||
--background-message-automod: {{colors.primary.default.hex}}15 !important;
|
||||
--background-message-automod-hover: {{colors.primary.default.hex}}20 !important;
|
||||
--background-message-highlight: {{colors.primary.default.hex}}15 !important;
|
||||
--background-message-highlight-hover: {{colors.primary.default.hex}}20 !important;
|
||||
|
||||
/* Discord unread messages banner specific variables */
|
||||
--background-mentioned: {{colors.primary.default.hex}}15 !important;
|
||||
--background-mentioned-hover: {{colors.primary.default.hex}}20 !important;
|
||||
--text-mentioned: {{colors.on_surface.default.hex}} !important;
|
||||
--text-mentioned-hover: {{colors.on_surface.default.hex}} !important;
|
||||
--text-mentioned-link: {{colors.primary.default.hex}} !important;
|
||||
|
||||
/* Additional Discord banner text variables */
|
||||
--text-normal: {{colors.on_surface.default.hex}} !important;
|
||||
--text-default: {{colors.on_surface.default.hex}} !important;
|
||||
--text-primary: {{colors.on_surface.default.hex}} !important;
|
||||
--text-secondary: {{colors.on_surface_variant.default.hex}} !important;
|
||||
--text-tertiary: {{colors.on_surface_variant.default.hex}} !important;
|
||||
--text-muted: {{colors.on_surface_variant.default.hex}} !important;
|
||||
--interactive-normal: {{colors.on_surface.default.hex}} !important;
|
||||
--interactive-muted: {{colors.on_surface_variant.default.hex}} !important;
|
||||
|
||||
/* Additional Discord banner variables */
|
||||
--background-message-automod: {{colors.primary.default.hex}}15 !important;
|
||||
--background-message-automod-hover: {{colors.primary.default.hex}}20 !important;
|
||||
--background-message-highlight: {{colors.primary.default.hex}}15 !important;
|
||||
--background-message-highlight-hover: {{colors.primary.default.hex}}20 !important;
|
||||
--background-message-hover: {{colors.surface_variant.default.hex}}50 !important;
|
||||
--background-modifier-hover: {{colors.surface_variant.default.hex}}80 !important;
|
||||
--background-modifier-selected: {{colors.primary.default.hex}}20 !important;
|
||||
--background-modifier-accent: {{colors.primary.default.hex}}30 !important;
|
||||
--background-modifier-active: {{colors.primary.default.hex}}25 !important;
|
||||
|
||||
/* Chat Input Improvements */
|
||||
--text-input-background: {{colors.surface_container.default.hex}} !important;
|
||||
--text-input-border: {{colors.outline.default.hex}} !important;
|
||||
--text-input-border-hover: {{colors.primary.default.hex}} !important;
|
||||
|
||||
/* Additional Discord-specific input variables */
|
||||
--deprecated-text-input-bg: {{colors.surface_container.default.hex}} !important;
|
||||
--deprecated-text-input-border: {{colors.outline.default.hex}} !important;
|
||||
--deprecated-text-input-border-hover: {{colors.primary.default.hex}} !important;
|
||||
--input-background: {{colors.surface_container.default.hex}} !important;
|
||||
--input-border: {{colors.outline.default.hex}} !important;
|
||||
--input-placeholder-text: {{colors.on_surface_variant.default.hex}} !important;
|
||||
|
||||
/* Elevated/Container Backgrounds */
|
||||
--background-tertiary: {{colors.surface_container.default.hex}} !important;
|
||||
--background-accent: {{colors.surface_container.default.hex}} !important;
|
||||
--background-surface-highest: {{colors.surface_container_high.default.hex}} !important;
|
||||
--background-base-lowest: {{colors.surface_container.default.hex}} !important;
|
||||
/* top left corner text */
|
||||
--corner-text: 'Midnight';
|
||||
|
||||
/* Border Colors */
|
||||
--border-faint: {{colors.outline_variant.default.hex}};
|
||||
--border-strong: {{colors.surface_container.default.hex}};
|
||||
--border-normal: {{colors.surface_container_high.default.hex}};
|
||||
--border-subtle: {{colors.surface.default.hex}} !important;
|
||||
--chat-border: {{colors.surface_container_high.default.hex}};
|
||||
/* color of status indicators and window controls */
|
||||
--online-indicator: {{colors.inverse_primary.default.hex}}; /* change to #23a55a for default green */
|
||||
--dnd-indicator: {{colors.error.default.hex}}; /* change to #f13f43 for default red */
|
||||
--idle-indicator: {{colors.tertiary_container.default.hex}}; /* change to #f0b232 for default yellow */
|
||||
--streaming-indicator: {{colors.on_primary.default.hex}}; /* change to #593695 for default purple */
|
||||
|
||||
/* Status Colors */
|
||||
--status-positive: {{colors.tertiary.default.hex}};
|
||||
--status-positive-background: {{colors.tertiary.default.hex}};
|
||||
--status-positive-text: {{colors.on_tertiary.default.hex}};
|
||||
--text-positive: {{colors.tertiary.default.hex}};
|
||||
--text-feedback-positive: {{colors.tertiary.default.hex}};
|
||||
--background-feedback-positive: {{colors.tertiary.default.hex}}20;
|
||||
--info-positive-background: {{colors.tertiary.default.hex}}20;
|
||||
--info-positive-foreground: {{colors.tertiary.default.hex}};
|
||||
--info-positive-text: {{colors.on_surface.default.hex}};
|
||||
/* accent colors */
|
||||
--accent-1: {{colors.tertiary.default.hex}}; /* links */
|
||||
--accent-2: {{colors.primary.default.hex}}; /* general unread/mention elements, some icons when active */
|
||||
--accent-3: {{colors.primary.default.hex}}; /* accent buttons */
|
||||
--accent-4: {{colors.surface_bright.default.hex}}; /* accent buttons when hovered */
|
||||
--accent-5: {{colors.primary_fixed_dim.default.hex}}; /* accent buttons when clicked */
|
||||
--mention: {{colors.surface.default.hex}}; /* mentions & mention messages */
|
||||
--mention-hover: {{colors.surface_bright.default.hex}}; /* mentions & mention messages when hovered */
|
||||
|
||||
--status-warning: {{colors.secondary.default.hex}};
|
||||
--status-warning-background: {{colors.secondary.default.hex}};
|
||||
--status-warning-text: {{colors.on_secondary.default.hex}};
|
||||
--text-warning: {{colors.secondary.default.hex}};
|
||||
--text-feedback-warning: {{colors.secondary.default.hex}};
|
||||
--background-feedback-warning: {{colors.secondary.default.hex}}20;
|
||||
--info-warning-background: {{colors.secondary.default.hex}}20;
|
||||
--info-warning-foreground: {{colors.secondary.default.hex}};
|
||||
--info-warning-text: {{colors.on_surface.default.hex}};
|
||||
/* text colors */
|
||||
--text-0: {{colors.surface.default.hex}}; /* text on colored elements */
|
||||
--text-1: {{colors.on_surface.default.hex}}; /* other normally white text */
|
||||
--text-2: {{colors.on_surface.default.hex}}; /* headings and important text */
|
||||
--text-3: {{colors.on_surface_variant.default.hex}}; /* normal text */
|
||||
--text-4: {{colors.on_surface_variant.default.hex}}; /* icon buttons and channels */
|
||||
--text-5: {{colors.outline.default.hex}}; /* muted channels/chats and timestamps */
|
||||
|
||||
--status-danger: {{colors.error.default.hex}};
|
||||
--status-danger-background: {{colors.error.default.hex}};
|
||||
--status-danger-text: {{colors.on_error.default.hex}};
|
||||
--text-danger: {{colors.error.default.hex}};
|
||||
--text-feedback-critical: {{colors.error.default.hex}};
|
||||
--background-feedback-critical: {{colors.error.default.hex}}20;
|
||||
--info-danger-background: {{colors.error.default.hex}}20;
|
||||
--info-danger-foreground: {{colors.error.default.hex}};
|
||||
--info-danger-text: {{colors.on_surface.default.hex}};
|
||||
/* background and dark colors */
|
||||
--bg-1: {{colors.primary.default.hex}}; /* dark buttons when clicked */
|
||||
--bg-2: {{colors.surface_container_high.default.hex}}; /* dark buttons */
|
||||
--bg-3: {{colors.surface_container_low.default.hex}}; /* spacing, secondary elements */
|
||||
--bg-4: {{colors.surface.default.hex}}; /* main background color */
|
||||
--hover: {{colors.surface_bright.default.hex}}; /* channels and buttons when hovered */
|
||||
--active: {{colors.surface_bright.default.hex}}; /* channels and buttons when clicked or selected */
|
||||
--message-hover: {{colors.surface_bright.default.hex}}; /* messages when hovered */
|
||||
|
||||
/* Button Colors */
|
||||
--button-secondary-background: {{colors.surface_variant.default.hex}} !important;
|
||||
--button-secondary-background-hover: {{colors.surface_container.default.hex}};
|
||||
--button-secondary-background-active: {{colors.surface_container.default.hex}};
|
||||
--button-secondary-background-disabled: {{colors.surface_variant.default.hex}};
|
||||
--button-secondary-text: {{colors.on_surface.default.hex}} !important;
|
||||
/* amount of spacing and padding */
|
||||
--spacing: 12px;
|
||||
|
||||
--button-filled-brand-text: {{colors.on_primary.default.hex}};
|
||||
--button-filled-brand-background: {{colors.primary.default.hex}};
|
||||
--button-filled-brand-background-hover: {{colors.primary.default.hex}};
|
||||
--button-filled-brand-background-active: {{colors.primary.default.hex}};
|
||||
/* animations */
|
||||
/* ALL ANIMATIONS CAN BE DISABLED WITH REDUCED MOTION IN DISCORD SETTINGS */
|
||||
--list-item-transition: 0.2s ease; /* channels/members/settings hover transition */
|
||||
--unread-bar-transition: 0.2s ease; /* unread bar moving into view transition */
|
||||
--moon-spin-transition: 0.4s ease; /* moon icon spin */
|
||||
--icon-spin-transition: 1s ease; /* round icon button spin (settings, emoji, etc.) */
|
||||
|
||||
/* Input Colors */
|
||||
--input-background: {{colors.surface_container.default.hex}};
|
||||
--input-border: {{colors.outline.default.hex}};
|
||||
--input-placeholder-text: {{colors.on_surface_variant.default.hex}};
|
||||
/* corner roundness (border-radius) */
|
||||
--roundness-xl: 22px; /* roundness of big panel outer corners */
|
||||
--roundness-l: 20px; /* popout panels */
|
||||
--roundness-m: 16px; /* smaller panels, images, embeds */
|
||||
--roundness-s: 12px; /* members, settings inputs */
|
||||
--roundness-xs: 10px; /* channels, buttons */
|
||||
--roundness-xxs: 8px; /* searchbar, small elements */
|
||||
|
||||
/* Scrollbar Colors */
|
||||
--scrollbar-thin-thumb: {{colors.primary.default.hex}};
|
||||
--scrollbar-thin-track: transparent;
|
||||
--scrollbar-auto-thumb: {{colors.primary.default.hex}};
|
||||
--scrollbar-auto-track: {{colors.surface_container_high.default.hex}};
|
||||
--scrollbar-auto-scrollbar-color-thumb: {{colors.primary.default.hex}};
|
||||
--scrollbar-auto-scrollbar-color-track: {{colors.surface_container_high.default.hex}};
|
||||
/* direct messages moon icon */
|
||||
/* change to block to show, none to hide */
|
||||
--discord-icon: none; /* discord icon */
|
||||
--moon-icon: block; /* moon icon */
|
||||
--moon-icon-url: url('https://upload.wikimedia.org/wikipedia/commons/c/c4/Font_Awesome_5_solid_moon.svg'); /* custom icon url */
|
||||
--moon-icon-size: auto;
|
||||
|
||||
/* Icon Colors */
|
||||
--icon-muted: {{colors.on_surface_variant.default.hex}};
|
||||
--icon-default: {{colors.on_surface.default.hex}};
|
||||
--icon-primary: {{colors.on_surface.default.hex}};
|
||||
--icon-secondary: {{colors.on_surface_variant.default.hex}};
|
||||
--icon-tertiary: {{colors.on_surface_variant.default.hex}} !important;
|
||||
|
||||
/* Channel Colors */
|
||||
--channels-default: {{colors.on_surface_variant.default.hex}} !important;
|
||||
--channel-icon: {{colors.on_surface_variant.default.hex}} !important;
|
||||
--channel-text-area-placeholder: {{colors.on_surface.default.hex}}80;
|
||||
|
||||
/* Selection and Hover States */
|
||||
--background-modifier-hover: {{colors.surface_variant.default.hex}}80;
|
||||
--background-modifier-selected: {{colors.primary.default.hex}}20 !important;
|
||||
--background-modifier-accent: {{colors.primary.default.hex}}30;
|
||||
--background-modifier-active: {{colors.primary.default.hex}}25 !important;
|
||||
--background-message-hover: {{colors.surface_variant.default.hex}}50 !important;
|
||||
--background-message-highlight: {{colors.primary.default.hex}}15;
|
||||
--background-message-highlight-hover: {{colors.primary.default.hex}}20;
|
||||
|
||||
/* Code Block - Use workspace background */
|
||||
--background-code: {{colors.surface_container.default.hex}};
|
||||
--textbox-markdown-syntax: {{colors.on_surface_variant.default.hex}};
|
||||
|
||||
/* Spoiler */
|
||||
--spoiler-revealed-background: {{colors.surface_container.default.hex}};
|
||||
--spoiler-hidden-background: {{colors.surface_variant.default.hex}};
|
||||
|
||||
/* White/Black Overrides */
|
||||
--white: {{colors.on_surface.default.hex}};
|
||||
--white-400: {{colors.on_surface.default.hex}};
|
||||
--white-500: {{colors.on_surface.default.hex}};
|
||||
--white-600: {{colors.on_surface_variant.default.hex}};
|
||||
--white-700: {{colors.on_surface_variant.default.hex}};
|
||||
--black-500: {{colors.surface_container_high.default.hex}};
|
||||
|
||||
/* Force styling for Discord unread messages banner */
|
||||
--unread-bar-background: {{colors.primary.default.hex}}15 !important;
|
||||
--unread-bar-text: {{colors.on_surface.default.hex}} !important;
|
||||
--unread-bar-hover: {{colors.primary.default.hex}}20 !important;
|
||||
|
||||
/* Additional Discord unread bar variables */
|
||||
--background-mentioned: {{colors.primary.default.hex}}15 !important;
|
||||
--background-mentioned-hover: {{colors.primary.default.hex}}20 !important;
|
||||
--text-mentioned: {{colors.on_surface.default.hex}} !important;
|
||||
--text-mentioned-hover: {{colors.on_surface.default.hex}} !important;
|
||||
--text-mentioned-link: {{colors.primary.default.hex}} !important;
|
||||
|
||||
/* Discord banner specific variables */
|
||||
--background-message-automod: {{colors.primary.default.hex}}15 !important;
|
||||
--background-message-automod-hover: {{colors.primary.default.hex}}20 !important;
|
||||
--background-message-highlight: {{colors.primary.default.hex}}15 !important;
|
||||
--background-message-highlight-hover: {{colors.primary.default.hex}}20 !important;
|
||||
|
||||
/* Discord unread bar specific variables */
|
||||
--background-mentioned: {{colors.primary.default.hex}}15 !important;
|
||||
--background-mentioned-hover: {{colors.primary.default.hex}}20 !important;
|
||||
--text-mentioned: {{colors.on_surface.default.hex}} !important;
|
||||
--text-mentioned-hover: {{colors.on_surface.default.hex}} !important;
|
||||
--text-mentioned-link: {{colors.primary.default.hex}} !important;
|
||||
|
||||
/* Additional Discord text variables that might affect the banner */
|
||||
--text-normal: {{colors.on_surface.default.hex}} !important;
|
||||
--text-default: {{colors.on_surface.default.hex}} !important;
|
||||
--text-primary: {{colors.on_surface.default.hex}} !important;
|
||||
--text-secondary: {{colors.on_surface_variant.default.hex}} !important;
|
||||
--text-tertiary: {{colors.on_surface_variant.default.hex}} !important;
|
||||
--text-muted: {{colors.on_surface_variant.default.hex}} !important;
|
||||
--interactive-normal: {{colors.on_surface.default.hex}} !important;
|
||||
--interactive-muted: {{colors.on_surface_variant.default.hex}} !important;
|
||||
|
||||
/* Force styling for Discord chat input */
|
||||
--chat-input-background: {{colors.surface_container.default.hex}} !important;
|
||||
--chat-input-placeholder: {{colors.on_surface_variant.default.hex}} !important;
|
||||
|
||||
/* Discord unread messages banner specific variables */
|
||||
--new-messages-bar-background: {{colors.surface_container.default.hex}} !important;
|
||||
--new-messages-bar-text: {{colors.on_surface.default.hex}} !important;
|
||||
--new-messages-bar-hover: {{colors.surface_container_high.default.hex}} !important;
|
||||
--bar-button-background: {{colors.surface_container.default.hex}} !important;
|
||||
--bar-button-text: {{colors.on_surface.default.hex}} !important;
|
||||
--bar-button-hover: {{colors.surface_container_high.default.hex}} !important;
|
||||
/* filter uncolorable elements to fit theme */
|
||||
/* (just set to none, they're too much work to configure) */
|
||||
--login-bg-filter: saturate(0.3) hue-rotate(-15deg) brightness(0.4); /* login background artwork */
|
||||
--green-to-accent-3-filter: hue-rotate(56deg) saturate(1.43); /* add friend page explore icon */
|
||||
--blurple-to-accent-3-filter: hue-rotate(304deg) saturate(0.84) brightness(1.2); /* add friend page school icon */
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark ::selection,
|
||||
.visual-refresh .theme-dark ::selection {
|
||||
background-color: {{colors.primary.default.hex}};
|
||||
/* Selected chat/friend text */
|
||||
.selected_f5eb4b,
|
||||
.selected_f6f816 .link_d8bfb3 {
|
||||
color: var(--text-0) !important;
|
||||
background: var(--accent-3) !important;
|
||||
}
|
||||
|
||||
/* Force Discord unread messages banner styling */
|
||||
.visual-refresh.theme-dark .newMessagesBar__0f481,
|
||||
.visual-refresh.theme-dark .barButtonMain__0f481,
|
||||
.visual-refresh.theme-dark .barButtonBase__0f481,
|
||||
.visual-refresh.theme-dark .span__0f481 {
|
||||
background-color: {{colors.surface_container.default.hex}} !important;
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
.selected_f6f816 .link_d8bfb3 * {
|
||||
color: var(--text-0) !important;
|
||||
fill: var(--text-0) !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .newMessagesBar__0f481:hover,
|
||||
.visual-refresh.theme-dark .barButtonMain__0f481:hover,
|
||||
.visual-refresh.theme-dark .barButtonBase__0f481:hover {
|
||||
background-color: {{colors.surface_container_high.default.hex}} !important;
|
||||
/* Make channel name text less visible (darker) */
|
||||
.name__2ea32 {
|
||||
color: var(--text-5) !important;
|
||||
opacity: 0.7 !important;
|
||||
}
|
||||
|
||||
/* Force Discord chat input styling */
|
||||
.visual-refresh.theme-dark .channelTextArea-rNsIhG,
|
||||
.visual-refresh.theme-dark .channelTextArea-rNsIhG *,
|
||||
.visual-refresh.theme-dark .scrollableContainer-2NUZem,
|
||||
.visual-refresh.theme-dark [data-slate-editor="true"] {
|
||||
background-color: {{colors.surface_container.default.hex}} !important;
|
||||
/* Make unread channel names brighter */
|
||||
.link__2ea32[aria-label*="unread"] .name__2ea32 {
|
||||
color: var(--text-2) !important;
|
||||
opacity: 1 !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark [data-slate-editor="true"]::placeholder,
|
||||
.visual-refresh.theme-dark .channelTextArea-rNsIhG [data-slate-editor="true"]::placeholder {
|
||||
color: {{colors.on_surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
/* Discord Emoji Picker Theming */
|
||||
.visual-refresh.theme-dark .contentWrapper__08434,
|
||||
.visual-refresh.theme-dark .emojiPicker_c0e32c,
|
||||
.visual-refresh.theme-dark .wrapper_c0e32c {
|
||||
background-color: {{colors.surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .nav__08434,
|
||||
.visual-refresh.theme-dark .navList__08434 {
|
||||
background-color: {{colors.surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .navButton__08434 {
|
||||
background-color: {{colors.surface.default.hex}} !important;
|
||||
color: {{colors.on_surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .navButtonActive__08434 {
|
||||
background-color: {{colors.surface.default.hex}} !important;
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .searchBar_c0e32c,
|
||||
.visual-refresh.theme-dark .input_a45028 {
|
||||
background-color: {{colors.surface.default.hex}} !important;
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .input_a45028::placeholder {
|
||||
color: {{colors.on_surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .header_c656ac,
|
||||
.visual-refresh.theme-dark .header__14245,
|
||||
.visual-refresh.theme-dark .wrapper__14245 {
|
||||
background-color: {{colors.surface_variant.default.hex}} !important;
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .headerLabel__14245 {
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .interactive__14245 {
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .header__14245 {
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .header__14245 * {
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .headerIcon__14245 svg,
|
||||
.visual-refresh.theme-dark .headerCollapseIcon__14245 svg {
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
fill: {{colors.on_surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .emojiItem_fc7141 {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .emojiItem_fc7141:hover {
|
||||
background-color: {{colors.surface_container.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .emojiItemSelected_fc7141 {
|
||||
background-color: {{colors.primary.default.hex}}20 !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .inspector_aeaaeb {
|
||||
background-color: {{colors.surface_container.default.hex}} !important;
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .categoryList_c0e32c {
|
||||
background-color: {{colors.surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .categoryItem_b9ee0c {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .categoryItem_b9ee0c:hover {
|
||||
background-color: {{colors.surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .categoryItemDefaultCategorySelected_b9ee0c {
|
||||
background-color: {{colors.surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
/* Additional Discord emoji picker elements */
|
||||
.visual-refresh.theme-dark .navItem__08434 {
|
||||
background-color: {{colors.surface_variant.default.hex}} !important;
|
||||
color: {{colors.on_surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .navItem__08434:hover {
|
||||
background-color: {{colors.surface_container.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .stickersNavItem__08434 {
|
||||
color: {{colors.on_surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .wrapper__14245 {
|
||||
background-color: {{colors.surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .headerLabel__14245 {
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .headerIcon__14245 svg,
|
||||
.visual-refresh.theme-dark .headerCollapseIcon__14245 svg {
|
||||
color: {{colors.on_surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .interactive__14245:hover {
|
||||
background-color: {{colors.surface_container.default.hex}} !important;
|
||||
}
|
||||
|
||||
/* Chat input styling */
|
||||
.visual-refresh.theme-dark .scrollableContainer__74017,
|
||||
.visual-refresh.theme-dark .themedBackground__74017,
|
||||
.visual-refresh.theme-dark .inner__74017,
|
||||
.visual-refresh.theme-dark .textArea__74017,
|
||||
.visual-refresh.theme-dark .slateContainer_ec4baf,
|
||||
.visual-refresh.theme-dark .markup__75297,
|
||||
.visual-refresh.theme-dark .editor__1b31f,
|
||||
.visual-refresh.theme-dark .slateTextArea_ec4baf {
|
||||
background-color: {{colors.surface_container.default.hex}} !important;
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .emptyText__1464f {
|
||||
color: {{colors.on_surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .placeholder__1b31f {
|
||||
color: {{colors.on_surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
/* Message content styling */
|
||||
.visual-refresh.theme-dark .messageContent_c19a55 {
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
background-color: {{colors.surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .messageContent_c19a55 .markup__75297 {
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
background-color: {{colors.surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
/* Message background styling */
|
||||
.visual-refresh.theme-dark .message__5126c,
|
||||
.visual-refresh.theme-dark .cozyMessage__5126c,
|
||||
.visual-refresh.theme-dark .wrapper_c19a55,
|
||||
.visual-refresh.theme-dark .contents_c19a55 {
|
||||
background-color: {{colors.surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
/* Message hover effects */
|
||||
.visual-refresh.theme-dark .message__5126c:hover {
|
||||
background-color: {{colors.surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .message__5126c:hover * {
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
/* Remove Discord's native quote/reply bar */
|
||||
.visual-refresh.theme-dark .message__5126c::before {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .message__5126c.hasReply_c19a55::before {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Channel styling - darker text for read channels */
|
||||
.visual-refresh.theme-dark .link__2ea32 .name__2ea32 {
|
||||
color: {{colors.outline.default.hex}} !important;
|
||||
}
|
||||
|
||||
/* Unread channels keep normal color */
|
||||
.visual-refresh.theme-dark .link__2ea32[aria-label*="unread"] .name__2ea32 {
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
/* Search input styling */
|
||||
.visual-refresh.theme-dark .inner_a45028 {
|
||||
background-color: {{colors.surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .input_a45028 {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .input_a45028::placeholder {
|
||||
color: {{colors.on_surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
/* Chat input placeholder styling */
|
||||
.visual-refresh.theme-dark .emptyText__1464f {
|
||||
color: {{colors.on_surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .slateTextArea_ec4baf > div:first-child .emptyText__1464f::before {
|
||||
content: "send a message" !important;
|
||||
color: {{colors.on_surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
/* Hide placeholder when input is focused */
|
||||
.visual-refresh.theme-dark .slateTextArea_ec4baf:focus .emptyText__1464f::before,
|
||||
.visual-refresh.theme-dark .markup__75297:focus .emptyText__1464f::before {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.visual-refresh.theme-dark .message__5126c:hover .messageContent_c19a55,
|
||||
.visual-refresh.theme-dark .message__5126c:hover .markup__75297,
|
||||
.visual-refresh.theme-dark .message__5126c:hover .header_c19a55,
|
||||
.visual-refresh.theme-dark .message__5126c:hover .headerText_c19a55,
|
||||
.visual-refresh.theme-dark .message__5126c:hover .username_c19a55,
|
||||
.visual-refresh.theme-dark .message__5126c:hover .timestamp_c19a55 {
|
||||
background-color: {{colors.surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.visual-refresh.theme-dark .categoryIcon_b9ee0c svg {
|
||||
color: {{colors.on_surface_variant.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .unicodeShortcut_b9ee0c {
|
||||
background-color: {{colors.surface_container.default.hex}} !important;
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .unicodeShortcut_b9ee0c:hover {
|
||||
background-color: {{colors.surface_container_high.default.hex}} !important;
|
||||
}
|
||||
|
||||
.visual-refresh.theme-dark .unicodeShortcut_b9ee0c svg {
|
||||
color: {{colors.on_surface.default.hex}} !important;
|
||||
}
|
||||
|
||||
/* Number badge styling */
|
||||
.visual-refresh.theme-dark .numberBadge__2b1f5 {
|
||||
color: {{colors.surface.default.hex}} !important;
|
||||
background-color: {{colors.primary.default.hex}} !important;
|
||||
}
|
||||
|
||||
/* New badge styling */
|
||||
.visual-refresh.theme-dark .newBadge__4ed1a {
|
||||
color: {{colors.surface.default.hex}} !important;
|
||||
background-color: {{colors.primary.default.hex}} !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 2.3 MiB |
|
Before Width: | Height: | Size: 2.1 MiB |
BIN
Assets/Screenshots/noctalia-dark-1.png
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
Assets/Screenshots/noctalia-dark-2.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
Assets/Screenshots/noctalia-dark-3.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
Assets/Screenshots/noctalia-light-1.png
Normal file
|
After Width: | Height: | Size: 4.8 MiB |
BIN
Assets/Screenshots/noctalia-light-2.png
Normal file
|
After Width: | Height: | Size: 4.0 MiB |
BIN
Assets/Screenshots/noctalia-light-3.png
Normal file
|
After Width: | Height: | Size: 3.7 MiB |
|
Before Width: | Height: | Size: 1.8 MiB |
@@ -4,4 +4,4 @@
|
||||
# Can be installed from AUR "qmlfmt-git"
|
||||
# Requires qt6-5compat
|
||||
|
||||
find . -name "*.qml" -print -exec qmlfmt -e -b 120 -t 2 -i 2 -w {} \;
|
||||
find . -name "*.qml" -print -exec qmlfmt -e -b 360 -t 2 -i 2 -w {} \;
|
||||
|
||||
@@ -34,8 +34,7 @@ Singleton {
|
||||
try {
|
||||
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
|
||||
return iconFromName(fallback, fallback)
|
||||
const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(
|
||||
appId) : DesktopEntries.byId(appId)
|
||||
const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId)
|
||||
const name = entry && entry.icon ? entry.icon : ""
|
||||
return iconFromName(name || fallback, fallback)
|
||||
} catch (e) {
|
||||
|
||||
@@ -14,7 +14,7 @@ Singleton {
|
||||
readonly property string defaultIcon: TablerIcons.defaultIcon
|
||||
readonly property var icons: TablerIcons.icons
|
||||
readonly property var aliases: TablerIcons.aliases
|
||||
readonly property string fontPath: "/Assets/Fonts/tabler/tabler-icons.woff2"
|
||||
readonly property string fontPath: "/Assets/Fonts/tabler/tabler-icons.ttf"
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.log("Icons", "Service started")
|
||||
|
||||
@@ -20,7 +20,8 @@ Singleton {
|
||||
"folder-open": "folder-open",
|
||||
"download": "download",
|
||||
"toast-notice": "circle-check",
|
||||
"toast-warning": "exclamation-circle",
|
||||
"toast-warning": "alert-circle",
|
||||
"toast-error": "circle-x",
|
||||
"question-mark": "question-mark",
|
||||
"search": "search",
|
||||
"warning": "exclamation-circle",
|
||||
@@ -39,21 +40,20 @@ Singleton {
|
||||
"balanced": "scale",
|
||||
"powersaver": "leaf",
|
||||
"storage": "database",
|
||||
"ethernet": "sitemap-filled",
|
||||
"ethernet": "sitemap",
|
||||
"keyboard": "keyboard",
|
||||
"shutdown": "power",
|
||||
"lock": "lock-filled",
|
||||
"lock": "lock",
|
||||
"logout": "logout",
|
||||
"reboot": "refresh",
|
||||
"suspend": "player-pause-filled",
|
||||
"nightlight-on": "moon-filled",
|
||||
"suspend": "player-pause",
|
||||
"nightlight-on": "moon",
|
||||
"nightlight-off": "moon-off",
|
||||
"nightlight-forced": "moon-stars",
|
||||
"bell": "bell",
|
||||
"bell-off": "bell-off",
|
||||
"keep-awake-on": "mug",
|
||||
"keep-awake-off": "mug-off",
|
||||
"panel": "clipboard-filled",
|
||||
"disc": "disc-filled",
|
||||
"image": "photo",
|
||||
"dark-mode": "contrast-filled",
|
||||
@@ -84,41 +84,47 @@ Singleton {
|
||||
"volume-zero": "volume-3",
|
||||
"volume-low": "volume-2",
|
||||
"volume-high": "volume",
|
||||
"weather-sun": "sun-filled",
|
||||
"weather-cloud-sun": "sun-wind",
|
||||
"weather-sun": "sun",
|
||||
"weather-cloud": "cloud",
|
||||
"weather-cloud-haze": "cloud-fog",
|
||||
"weather-cloud-lightning": "cloud-bolt",
|
||||
"weather-cloud-rain": "cloud-rain",
|
||||
"weather-cloud-snow": "cloud-snow",
|
||||
"weather-cloud-lightning": "cloud-bolt",
|
||||
"weather-cloud-sun": "cloud-sun",
|
||||
"brightness-low": "brightness-down-filled",
|
||||
"brightness-high": "brightness-up-filled",
|
||||
"settings-general": "adjustments-horizontal",
|
||||
"settings-bar": "capsule-horizontal",
|
||||
"settings-dock": "layout-bottombar",
|
||||
"settings-launcher": "rocket",
|
||||
"settings-audio": "device-speaker",
|
||||
"settings-display": "device-desktop",
|
||||
"settings-network": "sitemap-filled",
|
||||
"settings-brightness": "brightness-up-filled",
|
||||
"settings-weather": "cloud-rain",
|
||||
"settings-network": "sitemap",
|
||||
"settings-brightness": "brightness-up",
|
||||
"settings-weather": "cloud-sun",
|
||||
"settings-color-scheme": "palette",
|
||||
"settings-wallpaper": "paint",
|
||||
"settings-wallpaper-selector": "library-photo",
|
||||
"settings-screen-recorder": "video",
|
||||
"settings-hooks": "link",
|
||||
"settings-notification": "bell",
|
||||
"settings-about": "info-square-rounded",
|
||||
"bluetooth": "bluetooth",
|
||||
"bt-device-generic": "bluetooth",
|
||||
"bt-device-headphones": "headphones-filled",
|
||||
"bt-device-headphones": "headphones",
|
||||
"bt-device-mouse": "mouse-2",
|
||||
"bt-device-keyboard": "bluetooth",
|
||||
"bt-device-phone": "device-mobile-filled",
|
||||
"bt-device-phone": "device-mobile",
|
||||
"bt-device-watch": "device-watch",
|
||||
"bt-device-speaker": "device-speaker",
|
||||
"bt-device-tv": "device-tv"
|
||||
"bt-device-tv": "device-tv",
|
||||
"noctalia": "noctalia"
|
||||
}
|
||||
|
||||
// Fonts Codepoints - do not change
|
||||
// Fonts Codepoints - do not change!
|
||||
// Some icons have been disabled because Qt's text rendering engine recognizes
|
||||
// some ranges as special Unicode characters at a very low level.
|
||||
// ex: fe00-fe2f
|
||||
readonly property var icons: {
|
||||
"123": "\u{f554}",
|
||||
"360": "\u{f62f}",
|
||||
@@ -255,8 +261,8 @@ Singleton {
|
||||
"align-left": "\u{ea09}",
|
||||
"align-left-2": "\u{ff00}",
|
||||
"align-right": "\u{ea0a}",
|
||||
"align-right-2": "\u{feff}",
|
||||
"alpha": "\u{f543}",
|
||||
"alpha"//"align-right-2": "\u{feff}",
|
||||
: "\u{f543}",
|
||||
"alphabet-arabic": "\u{ff2f}",
|
||||
"alphabet-bangla": "\u{ff2e}",
|
||||
"alphabet-cyrillic": "\u{f1df}",
|
||||
@@ -2044,6 +2050,7 @@ Singleton {
|
||||
"cloud-snow": "\u{ea73}",
|
||||
"cloud-star": "\u{f85b}",
|
||||
"cloud-storm": "\u{ea74}",
|
||||
"cloud-sun": "\u{ea7a}",
|
||||
"cloud-up": "\u{f85c}",
|
||||
"cloud-upload": "\u{ea75}",
|
||||
"cloud-x": "\u{f85d}",
|
||||
@@ -3087,8 +3094,8 @@ Singleton {
|
||||
"friends": "\u{eab0}",
|
||||
"friends-off": "\u{f136}",
|
||||
"frustum": "\u{fa9f}",
|
||||
"frustum-off": "\u{fa9d}",
|
||||
"frustum-plus": "\u{fa9e}",
|
||||
"frustum-plus"//"frustum-off": "\u{fa9d}",
|
||||
: "\u{fa9e}",
|
||||
"function": "\u{f225}",
|
||||
"function-filled": "\u{fc2b}",
|
||||
"function-off": "\u{f3f0}",
|
||||
@@ -3347,13 +3354,13 @@ Singleton {
|
||||
"hexagon-letter-x": "\u{f479}",
|
||||
"hexagon-letter-x-filled": "\u{fe30}",
|
||||
"hexagon-letter-y": "\u{f47a}",
|
||||
"hexagon-letter-y-filled": "\u{fe2f}",
|
||||
"hexagon-letter-z": "\u{f47b}",
|
||||
"hexagon-letter-z-filled": "\u{fe2e}",
|
||||
"hexagon-minus": "\u{fc8f}",
|
||||
"hexagon-letter-z"//"hexagon-letter-y-filled": "\u{fe2f}",
|
||||
: "\u{f47b}",
|
||||
"hexagon-minus"//"hexagon-letter-z-filled": "\u{fe2e}",
|
||||
: "\u{fc8f}",
|
||||
"hexagon-minus-2": "\u{fc8e}",
|
||||
"hexagon-minus-filled": "\u{fe2d}",
|
||||
"hexagon-number-0": "\u{f459}",
|
||||
"hexagon-number-0"//"hexagon-minus-filled": "\u{fe2d}",
|
||||
: "\u{f459}",
|
||||
"hexagon-number-0-filled": "\u{f74c}",
|
||||
"hexagon-number-1": "\u{f45a}",
|
||||
"hexagon-number-1-filled": "\u{f74d}",
|
||||
@@ -3376,8 +3383,8 @@ Singleton {
|
||||
"hexagon-off": "\u{ee9c}",
|
||||
"hexagon-plus": "\u{fc45}",
|
||||
"hexagon-plus-2": "\u{fc90}",
|
||||
"hexagon-plus-filled": "\u{fe2c}",
|
||||
"hexagonal-prism": "\u{faa5}",
|
||||
"hexagonal-prism"//"hexagon-plus-filled": "\u{fe2c}",
|
||||
: "\u{faa5}",
|
||||
"hexagonal-prism-off": "\u{faa3}",
|
||||
"hexagonal-prism-plus": "\u{faa4}",
|
||||
"hexagonal-pyramid": "\u{faa8}",
|
||||
@@ -3407,8 +3414,8 @@ Singleton {
|
||||
"home-eco": "\u{f351}",
|
||||
"home-edit": "\u{f352}",
|
||||
"home-exclamation": "\u{f33c}",
|
||||
"home-filled": "\u{fe2b}",
|
||||
"home-hand": "\u{f504}",
|
||||
"home-hand"//"home-filled": "\u{fe2b}",
|
||||
: "\u{f504}",
|
||||
"home-heart": "\u{f353}",
|
||||
"home-infinity": "\u{f505}",
|
||||
"home-link": "\u{f354}",
|
||||
@@ -3525,8 +3532,8 @@ Singleton {
|
||||
"ironing-2-filled": "\u{1006e}",
|
||||
"ironing-3": "\u{f2f6}",
|
||||
"ironing-3-filled": "\u{1006d}",
|
||||
"ironing-filled": "\u{fe2a}",
|
||||
"ironing-off": "\u{f2f7}",
|
||||
"ironing-off"//"ironing-filled": "\u{fe2a}",
|
||||
: "\u{f2f7}",
|
||||
"ironing-steam": "\u{f2f9}",
|
||||
"ironing-steam-filled": "\u{1006c}",
|
||||
"ironing-steam-off": "\u{f2f8}",
|
||||
@@ -3536,8 +3543,8 @@ Singleton {
|
||||
"italic": "\u{eb93}",
|
||||
"jacket": "\u{f661}",
|
||||
"jetpack": "\u{f581}",
|
||||
"jetpack-filled": "\u{fe29}",
|
||||
"jewish-star": "\u{f3ff}",
|
||||
"jewish-star"//"jetpack-filled": "\u{fe29}",
|
||||
: "\u{f3ff}",
|
||||
"jewish-star-filled": "\u{f67e}",
|
||||
"join-bevel": "\u{ff4c}",
|
||||
"join-round": "\u{ff4b}",
|
||||
@@ -3551,8 +3558,8 @@ Singleton {
|
||||
"kering": "\u{efb8}",
|
||||
"kerning": "\u{efb8}",
|
||||
"key": "\u{eac7}",
|
||||
"key-filled": "\u{fe28}",
|
||||
"key-off": "\u{f14b}",
|
||||
"key-off"//"key-filled": "\u{fe28}",
|
||||
: "\u{f14b}",
|
||||
"keyboard": "\u{ebd6}",
|
||||
"keyboard-filled": "\u{100a2}",
|
||||
"keyboard-hide": "\u{ec7e}",
|
||||
@@ -3608,20 +3615,20 @@ Singleton {
|
||||
"layers-union": "\u{eacb}",
|
||||
"layout": "\u{eadb}",
|
||||
"layout-2": "\u{eacc}",
|
||||
"layout-2-filled": "\u{fe27}",
|
||||
"layout-align-bottom": "\u{eacd}",
|
||||
"layout-align-bottom-filled": "\u{fe26}",
|
||||
"layout-align-center": "\u{eace}",
|
||||
"layout-align-center-filled": "\u{fe25}",
|
||||
"layout-align-left": "\u{eacf}",
|
||||
"layout-align-left-filled": "\u{fe24}",
|
||||
"layout-align-middle": "\u{ead0}",
|
||||
"layout-align-middle-filled": "\u{fe23}",
|
||||
"layout-align-right": "\u{ead1}",
|
||||
"layout-align-right-filled": "\u{fe22}",
|
||||
"layout-align-top": "\u{ead2}",
|
||||
"layout-align-top-filled": "\u{fe21}",
|
||||
"layout-board": "\u{ef95}",
|
||||
"layout-align-left"//"layout-2-filled": "\u{fe27}",
|
||||
// "layout-align-bottom": "\u{eacd}",
|
||||
//"layout-align-bottom-filled": "\u{fe26}",
|
||||
// "layout-align-center": "\u{eace}",
|
||||
//"layout-align-center-filled": "\u{fe25}",
|
||||
: "\u{eacf}",
|
||||
"layout-align-middle"// "layout-align-left-filled": "\u{fe24}",
|
||||
: "\u{ead0}",
|
||||
"layout-align-right"//"layout-align-middle-filled": "\u{fe23}",
|
||||
: "\u{ead1}",
|
||||
"layout-align-top"//"layout-align-right-filled": "\u{fe22}",
|
||||
: "\u{ead2}",
|
||||
"layout-board"//"layout-align-top-filled": "\u{fe21}",
|
||||
: "\u{ef95}",
|
||||
"layout-board-filled": "\u{10182}",
|
||||
"layout-board-split": "\u{ef94}",
|
||||
"layout-board-split-filled": "\u{10183}",
|
||||
@@ -3633,8 +3640,8 @@ Singleton {
|
||||
"layout-bottombar-filled": "\u{fc37}",
|
||||
"layout-bottombar-inactive": "\u{fd45}",
|
||||
"layout-cards": "\u{ec13}",
|
||||
"layout-cards-filled": "\u{fe20}",
|
||||
"layout-collage": "\u{f389}",
|
||||
"layout-collage"// "layout-cards-filled": "\u{fe20}",
|
||||
: "\u{f389}",
|
||||
"layout-columns": "\u{ead4}",
|
||||
"layout-dashboard": "\u{f02c}",
|
||||
"layout-dashboard-filled": "\u{fe1f}",
|
||||
@@ -4115,14 +4122,14 @@ Singleton {
|
||||
"microphone": "\u{eaf0}",
|
||||
"microphone-2": "\u{ef2c}",
|
||||
"microphone-2-off": "\u{f40d}",
|
||||
"microphone-filled": "\u{fe0f}",
|
||||
"microphone-off": "\u{ed16}",
|
||||
"microphone-off"//"microphone-filled": "\u{fe0f}",
|
||||
: "\u{ed16}",
|
||||
"microscope": "\u{ef64}",
|
||||
"microscope-filled": "\u{10166}",
|
||||
"microscope-off": "\u{f40e}",
|
||||
"microwave": "\u{f248}",
|
||||
"microwave-filled": "\u{fe0e}",
|
||||
"microwave-off": "\u{f264}",
|
||||
"microwave-off"//"microwave-filled": "\u{fe0e}",
|
||||
: "\u{f264}",
|
||||
"military-award": "\u{f079}",
|
||||
"military-rank": "\u{efcf}",
|
||||
"military-rank-filled": "\u{ff5e}",
|
||||
@@ -4295,6 +4302,7 @@ Singleton {
|
||||
"news-off": "\u{f167}",
|
||||
"nfc": "\u{eeb7}",
|
||||
"nfc-off": "\u{f168}",
|
||||
"noctalia": "\u{ec33}",
|
||||
"no-copyright": "\u{efb9}",
|
||||
"no-creative-commons": "\u{efba}",
|
||||
"no-derivatives": "\u{efbb}",
|
||||
@@ -4354,18 +4362,18 @@ Singleton {
|
||||
"number-4-small": "\u{fcf9}",
|
||||
"number-40-small": "\u{fffa}",
|
||||
"number-41-small": "\u{fff9}",
|
||||
"number-42-small": "\u{fff8}",
|
||||
"number-43-small": "\u{fff7}",
|
||||
"number-44-small": "\u{fff6}",
|
||||
"number-45-small": "\u{fff5}",
|
||||
"number-46-small": "\u{fff4}",
|
||||
"number-47-small": "\u{fff3}",
|
||||
"number-48-small": "\u{fff2}",
|
||||
"number-49-small": "\u{fff1}",
|
||||
"number-5": "\u{edf5}",
|
||||
"number-5"//"number-42-small": "\u{fff8}",
|
||||
// "number-43-small": "\u{fff7}",
|
||||
// "number-44-small": "\u{fff6}",
|
||||
// "number-45-small": "\u{fff5}",
|
||||
// "number-46-small": "\u{fff4}",
|
||||
// "number-47-small": "\u{fff3}",
|
||||
// "number-48-small": "\u{fff2}",
|
||||
// "number-49-small": "\u{fff1}",
|
||||
: "\u{edf5}",
|
||||
"number-5-small": "\u{fcfa}",
|
||||
"number-50-small": "\u{fff0}",
|
||||
"number-51-small": "\u{ffef}",
|
||||
"number-51-small"// "number-50-small": "\u{fff0}",
|
||||
: "\u{ffef}",
|
||||
"number-52-small": "\u{ffee}",
|
||||
"number-53-small": "\u{ffed}",
|
||||
"number-54-small": "\u{ffec}",
|
||||
@@ -4805,11 +4813,11 @@ Singleton {
|
||||
"quote": "\u{efbe}",
|
||||
"quote-filled": "\u{1009c}",
|
||||
"quote-off": "\u{f188}",
|
||||
"quotes": "\u{fb1e}",
|
||||
"radar": "\u{f017}",
|
||||
"radar"//"quotes": "\u{fb1e}",
|
||||
: "\u{f017}",
|
||||
"radar-2": "\u{f016}",
|
||||
"radar-filled": "\u{fe0d}",
|
||||
"radar-off": "\u{f41f}",
|
||||
"radar-off"//"radar-filled": "\u{fe0d}",
|
||||
: "\u{f41f}",
|
||||
"radio": "\u{ef2d}",
|
||||
"radio-off": "\u{f420}",
|
||||
"radioactive": "\u{ecc0}",
|
||||
@@ -4869,12 +4877,12 @@ Singleton {
|
||||
"regex-off": "\u{f421}",
|
||||
"registered": "\u{eb14}",
|
||||
"relation-many-to-many": "\u{ed7f}",
|
||||
"relation-many-to-many-filled": "\u{fe0c}",
|
||||
"relation-one-to-many": "\u{ed80}",
|
||||
"relation-one-to-many-filled": "\u{fe0b}",
|
||||
"relation-one-to-one": "\u{ed81}",
|
||||
"relation-one-to-one-filled": "\u{fe0a}",
|
||||
"reload": "\u{f3ae}",
|
||||
"relation-one-to-many"//"relation-many-to-many-filled": "\u{fe0c}",
|
||||
: "\u{ed80}",
|
||||
"relation-one-to-one"//"relation-one-to-many-filled": "\u{fe0b}",
|
||||
: "\u{ed81}",
|
||||
"reload"//"relation-one-to-one-filled": "\u{fe0a}",
|
||||
: "\u{f3ae}",
|
||||
"reorder": "\u{fc15}",
|
||||
"repeat": "\u{eb72}",
|
||||
"repeat-off": "\u{f18e}",
|
||||
@@ -5026,8 +5034,8 @@ Singleton {
|
||||
"search": "\u{eb1c}",
|
||||
"search-off": "\u{f19c}",
|
||||
"section": "\u{eed5}",
|
||||
"section-filled": "\u{fe09}",
|
||||
"section-sign": "\u{f019}",
|
||||
"section-sign"//"section-filled": "\u{fe09}",
|
||||
: "\u{f019}",
|
||||
"seeding": "\u{ed51}",
|
||||
"seeding-filled": "\u{10006}",
|
||||
"seeding-off": "\u{f19d}",
|
||||
@@ -5235,8 +5243,8 @@ Singleton {
|
||||
"sort-z-a": "\u{f550}",
|
||||
"sos": "\u{f24a}",
|
||||
"soup": "\u{ef2e}",
|
||||
"soup-filled": "\u{fe08}",
|
||||
"soup-off": "\u{f42d}",
|
||||
"soup-off"//"soup-filled": "\u{fe08}",
|
||||
: "\u{f42d}",
|
||||
"source-code": "\u{f4a2}",
|
||||
"space": "\u{ec0c}",
|
||||
"space-off": "\u{f1aa}",
|
||||
@@ -5329,22 +5337,22 @@ Singleton {
|
||||
"square-half": "\u{effb}",
|
||||
"square-key": "\u{f638}",
|
||||
"square-letter-a": "\u{f47c}",
|
||||
"square-letter-a-filled": "\u{fe07}",
|
||||
"square-letter-b": "\u{f47d}",
|
||||
"square-letter-b-filled": "\u{fe06}",
|
||||
"square-letter-c": "\u{f47e}",
|
||||
"square-letter-c-filled": "\u{fe05}",
|
||||
"square-letter-d": "\u{f47f}",
|
||||
"square-letter-d-filled": "\u{fe04}",
|
||||
"square-letter-e": "\u{f480}",
|
||||
"square-letter-e-filled": "\u{fe03}",
|
||||
"square-letter-f": "\u{f481}",
|
||||
"square-letter-f-filled": "\u{fe02}",
|
||||
"square-letter-g": "\u{f482}",
|
||||
"square-letter-g-filled": "\u{fe01}",
|
||||
"square-letter-h": "\u{f483}",
|
||||
"square-letter-h-filled": "\u{fe00}",
|
||||
"square-letter-i": "\u{f484}",
|
||||
"square-letter-b"//"square-letter-a-filled": "\u{fe07}",
|
||||
: "\u{f47d}",
|
||||
"square-letter-c"//"square-letter-b-filled": "\u{fe06}",
|
||||
: "\u{f47e}",
|
||||
"square-letter-d"//"square-letter-c-filled": "\u{fe05}",
|
||||
: "\u{f47f}",
|
||||
"square-letter-e"//"square-letter-d-filled": "\u{fe04}",
|
||||
: "\u{f480}",
|
||||
"square-letter-f"//"square-letter-e-filled": "\u{fe03}",
|
||||
: "\u{f481}",
|
||||
"square-letter-g"//"square-letter-f-filled": "\u{fe02}",
|
||||
: "\u{f482}",
|
||||
"square-letter-h"//"square-letter-g-filled": "\u{fe01}",
|
||||
: "\u{f483}",
|
||||
"square-letter-i"//"square-letter-h-filled": "\u{fe00}",
|
||||
: "\u{f484}",
|
||||
"square-letter-i-filled": "\u{fdff}",
|
||||
"square-letter-j": "\u{f485}",
|
||||
"square-letter-j-filled": "\u{fdfe}",
|
||||
|
||||
205
Commons/KeyboardLayout.qml
Normal file
@@ -0,0 +1,205 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
// Comprehensive language name to ISO code mapping
|
||||
property var languageMap: {
|
||||
"english"// English variants
|
||||
: "us",
|
||||
"american": "us",
|
||||
"united states": "us",
|
||||
"us english": "us",
|
||||
"british": "gb",
|
||||
"uk": "ua",
|
||||
"united kingdom"// FIXED: Ukrainian language code should map to Ukraine
|
||||
: "gb",
|
||||
"english (uk)": "gb",
|
||||
"canadian": "ca",
|
||||
"canada": "ca",
|
||||
"canadian english": "ca",
|
||||
"australian": "au",
|
||||
"australia": "au",
|
||||
"swedish"// Nordic countries
|
||||
: "se",
|
||||
"svenska": "se",
|
||||
"sweden": "se",
|
||||
"norwegian": "no",
|
||||
"norsk": "no",
|
||||
"norway": "no",
|
||||
"danish": "dk",
|
||||
"dansk": "dk",
|
||||
"denmark": "dk",
|
||||
"finnish": "fi",
|
||||
"suomi": "fi",
|
||||
"finland": "fi",
|
||||
"icelandic": "is",
|
||||
"íslenska": "is",
|
||||
"iceland": "is",
|
||||
"german"// Western/Central European Germanic
|
||||
: "de",
|
||||
"deutsch": "de",
|
||||
"germany": "de",
|
||||
"austrian": "at",
|
||||
"austria": "at",
|
||||
"österreich": "at",
|
||||
"swiss": "ch",
|
||||
"switzerland": "ch",
|
||||
"schweiz": "ch",
|
||||
"suisse": "ch",
|
||||
"dutch": "nl",
|
||||
"nederlands": "nl",
|
||||
"netherlands": "nl",
|
||||
"holland": "nl",
|
||||
"belgian": "be",
|
||||
"belgium": "be",
|
||||
"belgië": "be",
|
||||
"belgique": "be",
|
||||
"french"// Romance languages (Western/Southern Europe)
|
||||
: "fr",
|
||||
"français": "fr",
|
||||
"france": "fr",
|
||||
"canadian french": "ca",
|
||||
"spanish": "es",
|
||||
"español": "es",
|
||||
"spain": "es",
|
||||
"castilian": "es",
|
||||
"italian": "it",
|
||||
"italiano": "it",
|
||||
"italy": "it",
|
||||
"portuguese": "pt",
|
||||
"português": "pt",
|
||||
"portugal": "pt",
|
||||
"catalan": "ad",
|
||||
"català": "ad",
|
||||
"andorra": "ad",
|
||||
"romanian"// Eastern European Romance
|
||||
: "ro",
|
||||
"română": "ro",
|
||||
"romania": "ro",
|
||||
"russian"// Slavic languages (Eastern Europe)
|
||||
: "ru",
|
||||
"русский": "ru",
|
||||
"russia": "ru",
|
||||
"polish": "pl",
|
||||
"polski": "pl",
|
||||
"poland": "pl",
|
||||
"czech": "cz",
|
||||
"čeština": "cz",
|
||||
"czech republic": "cz",
|
||||
"slovak": "sk",
|
||||
"slovenčina": "sk",
|
||||
"slovakia": "sk",
|
||||
"uk": "ua",
|
||||
"ukrainian"// Ukrainian language code
|
||||
: "ua",
|
||||
"українська": "ua",
|
||||
"ukraine": "ua",
|
||||
"bulgarian": "bg",
|
||||
"български": "bg",
|
||||
"bulgaria": "bg",
|
||||
"serbian": "rs",
|
||||
"srpski": "rs",
|
||||
"serbia": "rs",
|
||||
"croatian": "hr",
|
||||
"hrvatski": "hr",
|
||||
"croatia": "hr",
|
||||
"slovenian": "si",
|
||||
"slovenščina": "si",
|
||||
"slovenia": "si",
|
||||
"bosnian": "ba",
|
||||
"bosanski": "ba",
|
||||
"bosnia": "ba",
|
||||
"macedonian": "mk",
|
||||
"македонски": "mk",
|
||||
"macedonia": "mk",
|
||||
"irish"// Celtic languages (Western Europe)
|
||||
: "ie",
|
||||
"gaeilge": "ie",
|
||||
"ireland": "ie",
|
||||
"welsh": "gb",
|
||||
"cymraeg": "gb",
|
||||
"wales": "gb",
|
||||
"scottish": "gb",
|
||||
"gàidhlig": "gb",
|
||||
"scotland": "gb",
|
||||
"estonian"// Baltic languages (Northern Europe)
|
||||
: "ee",
|
||||
"eesti": "ee",
|
||||
"estonia": "ee",
|
||||
"latvian": "lv",
|
||||
"latviešu": "lv",
|
||||
"latvia": "lv",
|
||||
"lithuanian": "lt",
|
||||
"lietuvių": "lt",
|
||||
"lithuania": "lt",
|
||||
"hungarian"// Other European languages
|
||||
: "hu",
|
||||
"magyar": "hu",
|
||||
"hungary": "hu",
|
||||
"greek": "gr",
|
||||
"ελληνικά": "gr",
|
||||
"greece": "gr",
|
||||
"albanian": "al",
|
||||
"shqip": "al",
|
||||
"albania": "al",
|
||||
"maltese": "mt",
|
||||
"malti": "mt",
|
||||
"malta": "mt",
|
||||
"turkish"// West/Southwest Asian languages
|
||||
: "tr",
|
||||
"türkçe": "tr",
|
||||
"turkey": "tr",
|
||||
"arabic": "ar",
|
||||
"العربية": "ar",
|
||||
"arab": "ar",
|
||||
"hebrew": "il",
|
||||
"עברית": "il",
|
||||
"israel": "il",
|
||||
"brazilian"// South American languages
|
||||
: "br",
|
||||
"brazilian portuguese": "br",
|
||||
"brasil": "br",
|
||||
"brazil": "br",
|
||||
"japanese"// East Asian languages
|
||||
: "jp",
|
||||
"日本語": "jp",
|
||||
"japan": "jp",
|
||||
"korean": "kr",
|
||||
"한국어": "kr",
|
||||
"korea": "kr",
|
||||
"south korea": "kr",
|
||||
"chinese": "cn",
|
||||
"中文": "cn",
|
||||
"china": "cn",
|
||||
"simplified chinese": "cn",
|
||||
"traditional chinese": "tw",
|
||||
"taiwan": "tw",
|
||||
"繁體中文": "tw",
|
||||
"thai"// Southeast Asian languages
|
||||
: "th",
|
||||
"ไทย": "th",
|
||||
"thailand": "th",
|
||||
"vietnamese": "vn",
|
||||
"tiếng việt": "vn",
|
||||
"vietnam": "vn",
|
||||
"hindi"// South Asian languages
|
||||
: "in",
|
||||
"हिन्दी": "in",
|
||||
"india": "in",
|
||||
"afrikaans"// African languages
|
||||
: "za",
|
||||
"south africa": "za",
|
||||
"south african": "za",
|
||||
"qwerty"// Layout variants
|
||||
: "us",
|
||||
"dvorak": "us",
|
||||
"colemak": "us",
|
||||
"workman": "us",
|
||||
"azerty": "fr",
|
||||
"norman": "fr",
|
||||
"qwertz": "de"
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,8 @@ Singleton {
|
||||
// Default config directory: ~/.config/noctalia
|
||||
// Default cache directory: ~/.cache/noctalia
|
||||
property string shellName: "noctalia"
|
||||
property string configDir: Quickshell.env("NOCTALIA_CONFIG_DIR") || (Quickshell.env("XDG_CONFIG_HOME")
|
||||
|| Quickshell.env(
|
||||
"HOME") + "/.config") + "/" + shellName + "/"
|
||||
property string cacheDir: Quickshell.env("NOCTALIA_CACHE_DIR") || (Quickshell.env("XDG_CACHE_HOME") || Quickshell.env(
|
||||
"HOME") + "/.cache") + "/" + shellName + "/"
|
||||
property string configDir: Quickshell.env("NOCTALIA_CONFIG_DIR") || (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + "/" + shellName + "/"
|
||||
property string cacheDir: Quickshell.env("NOCTALIA_CACHE_DIR") || (Quickshell.env("XDG_CACHE_HOME") || Quickshell.env("HOME") + "/.cache") + "/" + shellName + "/"
|
||||
property string cacheDirImages: cacheDir + "images/"
|
||||
|
||||
property string settingsFile: Quickshell.env("NOCTALIA_SETTINGS_FILE") || (configDir + "settings.json")
|
||||
@@ -58,8 +55,7 @@ Singleton {
|
||||
}
|
||||
}
|
||||
if (!hasValidBarMonitor) {
|
||||
Logger.warn("Settings",
|
||||
"No configured bar monitors found on system, clearing bar monitor list to show on all screens")
|
||||
Logger.warn("Settings", "No configured bar monitors found on system, clearing bar monitor list to show on all screens")
|
||||
adapter.bar.monitors = []
|
||||
} else {
|
||||
|
||||
@@ -138,13 +134,18 @@ Singleton {
|
||||
widget.showIcon = widget.showIcon !== undefined ? widget.showIcon : adapter.bar.showActiveWindowIcon
|
||||
break
|
||||
case "Battery":
|
||||
widget.alwaysShowPercentage = widget.alwaysShowPercentage
|
||||
!== undefined ? widget.alwaysShowPercentage : adapter.bar.alwaysShowBatteryPercentage
|
||||
widget.alwaysShowPercentage = widget.alwaysShowPercentage !== undefined ? widget.alwaysShowPercentage : adapter.bar.alwaysShowBatteryPercentage
|
||||
break
|
||||
case "Clock":
|
||||
widget.showDate = widget.showDate !== undefined ? widget.showDate : adapter.location.showDateWithClock
|
||||
widget.use12HourClock = widget.use12HourClock !== undefined ? widget.use12HourClock : adapter.location.use12HourClock
|
||||
widget.reverseDayMonth = widget.reverseDayMonth !== undefined ? widget.reverseDayMonth : adapter.location.reverseDayMonth
|
||||
if (widget.showDate !== undefined) {
|
||||
widget.displayFormat = "time-date"
|
||||
} else if (widget.showSeconds) {
|
||||
widget.displayFormat = "time-seconds"
|
||||
}
|
||||
delete widget.showDate
|
||||
delete widget.showSeconds
|
||||
break
|
||||
case "MediaMini":
|
||||
widget.showAlbumArt = widget.showAlbumArt !== undefined ? widget.showAlbumArt : adapter.audio.showMiniplayerAlbumArt
|
||||
@@ -174,7 +175,7 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
// Backup the widget definition before altering
|
||||
// Compare settings, to detect if something has been upgraded
|
||||
const widgetAfter = JSON.stringify(widget)
|
||||
return (widgetAfter !== widgetBefore)
|
||||
}
|
||||
@@ -258,14 +259,19 @@ Singleton {
|
||||
JsonAdapter {
|
||||
id: adapter
|
||||
|
||||
property int settingsVersion: 1
|
||||
property int settingsVersion: 2
|
||||
|
||||
// bar
|
||||
property JsonObject bar: JsonObject {
|
||||
property string position: "top" // "top" or "bottom"
|
||||
property string position: "top" // "top", "bottom", "left", or "right"
|
||||
property real backgroundOpacity: 1.0
|
||||
property list<string> monitors: []
|
||||
|
||||
// Floating bar settings
|
||||
property bool floating: false
|
||||
property real marginVertical: 0.25
|
||||
property real marginHorizontal: 0.25
|
||||
|
||||
property bool showActiveWindowIcon: true // TODO: delete
|
||||
property bool alwaysShowBatteryPercentage: false // TODO: delete
|
||||
property bool showNetworkStats: false // TODO: delete
|
||||
@@ -316,7 +322,9 @@ Singleton {
|
||||
property string avatarImage: defaultAvatar
|
||||
property bool dimDesktop: false
|
||||
property bool showScreenCorners: false
|
||||
property bool forceBlackScreenCorners: false
|
||||
property real radiusRatio: 1.0
|
||||
property real screenRadiusRatio: 1.0
|
||||
// Animation speed multiplier (0.1x - 2.0x)
|
||||
property real animationSpeed: 1.0
|
||||
}
|
||||
@@ -376,6 +384,7 @@ Singleton {
|
||||
property bool autoHide: false
|
||||
property bool exclusive: false
|
||||
property real backgroundOpacity: 1.0
|
||||
property real floatingRatio: 1.0
|
||||
property list<string> monitors: []
|
||||
}
|
||||
|
||||
@@ -391,6 +400,10 @@ Singleton {
|
||||
property list<string> monitors: []
|
||||
// Last time the user opened the notification history (ms since epoch)
|
||||
property real lastSeenTs: 0
|
||||
// Duration settings for different urgency levels (in seconds)
|
||||
property int lowUrgencyDuration: 3
|
||||
property int normalUrgencyDuration: 8
|
||||
property int criticalUrgencyDuration: 15
|
||||
}
|
||||
|
||||
// audio
|
||||
@@ -437,6 +450,7 @@ Singleton {
|
||||
property bool foot: false
|
||||
property bool fuzzel: false
|
||||
property bool vesktop: false
|
||||
property bool pywalfox: false
|
||||
property bool enableUserTemplates: false
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,9 @@ Singleton {
|
||||
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
|
||||
|
||||
// Border
|
||||
property int borderS: 1
|
||||
property int borderM: 2
|
||||
@@ -63,9 +66,9 @@ Singleton {
|
||||
property int animationSlowest: Math.round(750 / Settings.data.general.animationSpeed)
|
||||
|
||||
// Dimensions
|
||||
property int barHeight: 36
|
||||
property int barHeight: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 39 : 37
|
||||
property int capsuleHeight: (barHeight * 0.73)
|
||||
property int baseWidgetSize: 32
|
||||
property int baseWidgetSize: (barHeight * 0.9)
|
||||
property int sliderWidth: 200
|
||||
|
||||
// Delays
|
||||
|
||||
@@ -43,9 +43,7 @@ Variants {
|
||||
|
||||
// Fillmode default is "crop"
|
||||
property real fillMode: 1.0
|
||||
property vector4d fillColor: Qt.vector4d(Settings.data.wallpaper.fillColor.r,
|
||||
Settings.data.wallpaper.fillColor.g,
|
||||
Settings.data.wallpaper.fillColor.b, 1.0)
|
||||
property vector4d fillColor: Qt.vector4d(Settings.data.wallpaper.fillColor.r, Settings.data.wallpaper.fillColor.g, Settings.data.wallpaper.fillColor.b, 1.0)
|
||||
|
||||
// On startup assign wallpaper immediately
|
||||
Component.onCompleted: {
|
||||
@@ -229,8 +227,7 @@ Variants {
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
// The stripes shader feels faster visually, we make it a bit slower here.
|
||||
duration: transitionType == "stripes" ? Settings.data.wallpaper.transitionDuration
|
||||
* 1.6 : Settings.data.wallpaper.transitionDuration
|
||||
duration: transitionType == "stripes" ? Settings.data.wallpaper.transitionDuration * 1.6 : Settings.data.wallpaper.transitionDuration
|
||||
easing.type: Easing.InOutCubic
|
||||
onFinished: {
|
||||
// Swap images after transition completes
|
||||
|
||||
@@ -60,6 +60,7 @@ Variants {
|
||||
MultiEffect {
|
||||
anchors.fill: parent
|
||||
source: bgImage
|
||||
autoPaddingEnabled: false
|
||||
blurEnabled: true
|
||||
blur: 0.48
|
||||
blurMax: 128
|
||||
@@ -68,9 +69,7 @@ Variants {
|
||||
// Make the overview darker
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Settings.data.colorSchemes.darkMode ? Qt.alpha(Color.mSurface,
|
||||
Style.opacityMedium) : Qt.alpha(Color.mOnSurface,
|
||||
Style.opacityMedium)
|
||||
color: Settings.data.colorSchemes.darkMode ? Qt.alpha(Color.mSurface, Style.opacityMedium) : Qt.alpha(Color.mOnSurface, Style.opacityMedium)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ Loader {
|
||||
property real scaling: ScalingService.getScreenScale(screen)
|
||||
screen: modelData
|
||||
|
||||
property color cornerColor: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
|
||||
property real cornerRadius: 20 * scaling
|
||||
property real cornerSize: 20 * scaling
|
||||
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
|
||||
@@ -46,12 +46,12 @@ Loader {
|
||||
}
|
||||
|
||||
margins {
|
||||
top: ((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: ((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
|
||||
// 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 && ((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 && ((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 && ((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 && ((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
|
||||
}
|
||||
|
||||
mask: Region {}
|
||||
|
||||
@@ -27,118 +27,222 @@ Variants {
|
||||
}
|
||||
}
|
||||
|
||||
active: Settings.isLoaded && modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name)
|
||||
|| (Settings.data.bar.monitors.length === 0)) : false
|
||||
active: Settings.isLoaded && modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
screen: modelData || null
|
||||
|
||||
WlrLayershell.namespace: "noctalia-bar"
|
||||
|
||||
implicitHeight: Math.round(Style.barHeight * scaling)
|
||||
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
|
||||
color: Color.transparent
|
||||
|
||||
anchors {
|
||||
top: Settings.data.bar.position === "top"
|
||||
bottom: Settings.data.bar.position === "bottom"
|
||||
left: true
|
||||
right: true
|
||||
top: Settings.data.bar.position === "top" || Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
bottom: Settings.data.bar.position === "bottom" || Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
left: Settings.data.bar.position === "left" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom"
|
||||
right: Settings.data.bar.position === "right" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom"
|
||||
}
|
||||
|
||||
// Floating bar margins - only apply when floating is enabled
|
||||
margins {
|
||||
top: Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
|
||||
bottom: Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
|
||||
left: Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
|
||||
right: Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
// Background fill
|
||||
// Background fill with shadow
|
||||
Rectangle {
|
||||
id: bar
|
||||
|
||||
anchors.fill: parent
|
||||
color: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
|
||||
|
||||
// Floating bar rounded corners
|
||||
radius: Settings.data.bar.floating ? Style.radiusL : 0
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Left Section - Dynamic Widgets
|
||||
Row {
|
||||
id: leftSection
|
||||
objectName: "leftSection"
|
||||
// For vertical bars, use a single column layout
|
||||
Loader {
|
||||
id: verticalBarLayout
|
||||
anchors.fill: parent
|
||||
visible: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
sourceComponent: verticalBarComponent
|
||||
}
|
||||
|
||||
height: parent.height
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.marginS * scaling
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
// For horizontal bars, use the original three-section layout
|
||||
Loader {
|
||||
id: horizontalBarLayout
|
||||
anchors.fill: parent
|
||||
visible: Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom"
|
||||
sourceComponent: horizontalBarComponent
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.bar.widgets.left
|
||||
delegate: NWidgetLoader {
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
"scaling": ScalingService.getScreenScale(screen),
|
||||
"widgetId": modelData.id,
|
||||
"barSection": parent.objectName,
|
||||
"sectionWidgetIndex": index,
|
||||
"sectionWidgetsCount": Settings.data.bar.widgets.left.length
|
||||
// Main layout components
|
||||
Component {
|
||||
id: verticalBarComponent
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
// Top section (left widgets)
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Style.marginM * root.scaling
|
||||
spacing: Style.marginS * root.scaling
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.bar.widgets.left
|
||||
delegate: NWidgetLoader {
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
"scaling": ScalingService.getScreenScale(screen),
|
||||
"widgetId": modelData.id,
|
||||
"section": "left",
|
||||
"sectionWidgetIndex": index,
|
||||
"sectionWidgetsCount": Settings.data.bar.widgets.left.length
|
||||
}
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Center section (center widgets)
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * root.scaling
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.bar.widgets.center
|
||||
delegate: NWidgetLoader {
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
"scaling": ScalingService.getScreenScale(screen),
|
||||
"widgetId": modelData.id,
|
||||
"section": "center",
|
||||
"sectionWidgetIndex": index,
|
||||
"sectionWidgetsCount": Settings.data.bar.widgets.center.length
|
||||
}
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom section (right widgets)
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Style.marginM * root.scaling
|
||||
spacing: Style.marginS * root.scaling
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.bar.widgets.right
|
||||
delegate: NWidgetLoader {
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
"scaling": ScalingService.getScreenScale(screen),
|
||||
"widgetId": modelData.id,
|
||||
"section": "right",
|
||||
"sectionWidgetIndex": index,
|
||||
"sectionWidgetsCount": Settings.data.bar.widgets.right.length
|
||||
}
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Center Section - Dynamic Widgets
|
||||
Row {
|
||||
id: centerSection
|
||||
objectName: "centerSection"
|
||||
Component {
|
||||
id: horizontalBarComponent
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
height: parent.height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.bar.widgets.center
|
||||
delegate: NWidgetLoader {
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
"scaling": ScalingService.getScreenScale(screen),
|
||||
"widgetId": modelData.id,
|
||||
"barSection": parent.objectName,
|
||||
"sectionWidgetIndex": index,
|
||||
"sectionWidgetsCount": Settings.data.bar.widgets.center.length
|
||||
}
|
||||
// Left Section
|
||||
RowLayout {
|
||||
id: leftSection
|
||||
objectName: "leftSection"
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.marginS * root.scaling
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * root.scaling
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.bar.widgets.left
|
||||
delegate: NWidgetLoader {
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
"scaling": ScalingService.getScreenScale(screen),
|
||||
"widgetId": modelData.id,
|
||||
"section": "left",
|
||||
"sectionWidgetIndex": index,
|
||||
"sectionWidgetsCount": Settings.data.bar.widgets.left.length
|
||||
}
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Right Section - Dynamic Widgets
|
||||
Row {
|
||||
id: rightSection
|
||||
objectName: "rightSection"
|
||||
|
||||
height: parent.height
|
||||
anchors.right: bar.right
|
||||
anchors.rightMargin: Style.marginS * scaling
|
||||
anchors.verticalCenter: bar.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.bar.widgets.right
|
||||
delegate: NWidgetLoader {
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
"scaling": ScalingService.getScreenScale(screen),
|
||||
"widgetId": modelData.id,
|
||||
"barSection": parent.objectName,
|
||||
"sectionWidgetIndex": index,
|
||||
"sectionWidgetsCount": Settings.data.bar.widgets.right.length
|
||||
}
|
||||
// Center Section
|
||||
RowLayout {
|
||||
id: centerSection
|
||||
objectName: "centerSection"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * root.scaling
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.bar.widgets.center
|
||||
delegate: NWidgetLoader {
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
"scaling": ScalingService.getScreenScale(screen),
|
||||
"widgetId": modelData.id,
|
||||
"section": "center",
|
||||
"sectionWidgetIndex": index,
|
||||
"sectionWidgetsCount": Settings.data.bar.widgets.center.length
|
||||
}
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Right Section
|
||||
RowLayout {
|
||||
id: rightSection
|
||||
objectName: "rightSection"
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.marginS * root.scaling
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * root.scaling
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.bar.widgets.right
|
||||
delegate: NWidgetLoader {
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
"scaling": ScalingService.getScreenScale(screen),
|
||||
"widgetId": modelData.id,
|
||||
"section": "right",
|
||||
"sectionWidgetIndex": index,
|
||||
"sectionWidgetsCount": Settings.data.bar.widgets.right.length
|
||||
}
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@ PopupWindow {
|
||||
implicitWidth: menuWidth * scaling
|
||||
|
||||
// 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 * scaling))
|
||||
visible: false
|
||||
color: Color.transparent
|
||||
anchor.item: anchorItem
|
||||
@@ -159,8 +158,7 @@ PopupWindow {
|
||||
NText {
|
||||
id: text
|
||||
Layout.fillWidth: true
|
||||
color: (modelData?.enabled
|
||||
?? true) ? (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) : Color.mOnSurfaceVariant
|
||||
color: (modelData?.enabled ?? true) ? (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface) : Color.mOnSurfaceVariant
|
||||
text: modelData?.text !== "" ? modelData?.text.replace(/[\n\r]+/g, ' ') : "..."
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -180,7 +178,7 @@ PopupWindow {
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
visible: modelData?.hasChildren ?? false
|
||||
color: Color.mOnSurface
|
||||
color: (mouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,9 +220,32 @@ PopupWindow {
|
||||
const submenuWidth = menuWidth * scaling // Assuming a similar width as the parent
|
||||
const overlap = 4 * scaling // A small overlap to bridge the mouse path
|
||||
|
||||
// Check if there's enough space on the right
|
||||
// Determine submenu opening direction based on bar position and available space
|
||||
let openLeft = false
|
||||
|
||||
// Check bar position first
|
||||
const barPosition = Settings.data.bar.position
|
||||
const globalPos = entry.mapToGlobal(0, 0)
|
||||
const openLeft = (globalPos.x + entry.width + submenuWidth > (screen ? screen.width : Screen.width))
|
||||
|
||||
if (barPosition === "right") {
|
||||
// Bar is on the right, prefer opening submenus to the left
|
||||
openLeft = true
|
||||
} else if (barPosition === "left") {
|
||||
// Bar is on the left, prefer opening submenus to the right
|
||||
openLeft = false
|
||||
} else {
|
||||
// Bar is horizontal (top/bottom) or undefined, use space-based logic
|
||||
openLeft = (globalPos.x + entry.width + submenuWidth > (screen ? screen.width : Screen.width))
|
||||
|
||||
// Secondary check: ensure we don't open off-screen
|
||||
if (openLeft && globalPos.x - submenuWidth < 0) {
|
||||
// Would open off the left edge, force right opening
|
||||
openLeft = false
|
||||
} else if (!openLeft && globalPos.x + entry.width + submenuWidth > (screen ? screen.width : Screen.width)) {
|
||||
// Would open off the right edge, force left opening
|
||||
openLeft = true
|
||||
}
|
||||
}
|
||||
|
||||
// Position with overlap
|
||||
const anchorX = openLeft ? -submenuWidth + overlap : entry.width - overlap
|
||||
|
||||
@@ -8,20 +8,19 @@ import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
RowLayout {
|
||||
Item {
|
||||
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 barSection: ""
|
||||
property string section: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
@@ -37,34 +36,85 @@ RowLayout {
|
||||
readonly property real minWidth: Math.max(1, screen.width * 0.06)
|
||||
readonly property real maxWidth: minWidth * 2
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
implicitHeight: (barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling)
|
||||
implicitWidth: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (horizontalLayout.implicitWidth + Style.marginM * 2 * scaling)
|
||||
|
||||
function getTitle() {
|
||||
return CompositorService.focusedWindowTitle !== "(No active window)" ? CompositorService.focusedWindowTitle : ""
|
||||
try {
|
||||
return CompositorService.focusedWindowTitle !== "(No active window)" ? CompositorService.focusedWindowTitle : ""
|
||||
} catch (e) {
|
||||
Logger.warn("ActiveWindow", "Error getting title:", e)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Style.marginS * scaling
|
||||
visible: getTitle() !== ""
|
||||
|
||||
function calculatedVerticalHeight() {
|
||||
// Use standard widget height like other widgets
|
||||
return Math.round(Style.capsuleHeight * scaling)
|
||||
}
|
||||
|
||||
function calculatedHorizontalWidth() {
|
||||
let total = Style.marginM * 2 * scaling // internal padding
|
||||
|
||||
if (showIcon) {
|
||||
total += Style.baseWidgetSize * 0.5 * scaling + 2 * scaling // icon + spacing
|
||||
}
|
||||
|
||||
// Calculate actual text width more accurately
|
||||
const title = getTitle()
|
||||
if (title !== "") {
|
||||
// Estimate text width: average character width * number of characters
|
||||
const avgCharWidth = Style.fontSizeS * scaling * 0.6 // rough estimate
|
||||
const titleWidth = Math.min(title.length * avgCharWidth, 80 * scaling)
|
||||
total += titleWidth
|
||||
}
|
||||
|
||||
// Row layout handles spacing between widgets
|
||||
return Math.max(total, Style.capsuleHeight * scaling) // Minimum width
|
||||
}
|
||||
|
||||
function getAppIcon() {
|
||||
// Try CompositorService first
|
||||
const focusedWindow = CompositorService.getFocusedWindow()
|
||||
if (focusedWindow && focusedWindow.appId) {
|
||||
const idValue = focusedWindow.appId
|
||||
const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue)
|
||||
return AppIcons.iconForAppId(normalizedId.toLowerCase())
|
||||
}
|
||||
|
||||
// Fallback to ToplevelManager
|
||||
if (ToplevelManager && ToplevelManager.activeToplevel) {
|
||||
const activeToplevel = ToplevelManager.activeToplevel
|
||||
if (activeToplevel.appId) {
|
||||
const idValue2 = activeToplevel.appId
|
||||
const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2)
|
||||
return AppIcons.iconForAppId(normalizedId2.toLowerCase())
|
||||
try {
|
||||
// Try CompositorService first
|
||||
const focusedWindow = CompositorService.getFocusedWindow()
|
||||
if (focusedWindow && focusedWindow.appId) {
|
||||
try {
|
||||
const idValue = focusedWindow.appId
|
||||
const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue)
|
||||
const iconResult = AppIcons.iconForAppId(normalizedId.toLowerCase())
|
||||
if (iconResult && iconResult !== "") {
|
||||
return iconResult
|
||||
}
|
||||
} catch (iconError) {
|
||||
Logger.warn("ActiveWindow", "Error getting icon from CompositorService:", iconError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
// Fallback to ToplevelManager
|
||||
if (ToplevelManager && ToplevelManager.activeToplevel) {
|
||||
try {
|
||||
const activeToplevel = ToplevelManager.activeToplevel
|
||||
if (activeToplevel.appId) {
|
||||
const idValue2 = activeToplevel.appId
|
||||
const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2)
|
||||
const iconResult2 = AppIcons.iconForAppId(normalizedId2.toLowerCase())
|
||||
if (iconResult2 && iconResult2 !== "") {
|
||||
return iconResult2
|
||||
}
|
||||
}
|
||||
} catch (fallbackError) {
|
||||
Logger.warn("ActiveWindow", "Error getting icon from ToplevelManager:", fallbackError)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
} catch (e) {
|
||||
Logger.warn("ActiveWindow", "Error in getAppIcon:", e)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// A hidden text element to safely measure the full title width
|
||||
@@ -79,27 +129,31 @@ RowLayout {
|
||||
Rectangle {
|
||||
id: windowTitleRect
|
||||
visible: root.visible
|
||||
Layout.preferredWidth: contentLayout.implicitWidth + Style.marginM * 2 * scaling
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * scaling) : (horizontalLayout.implicitWidth + Style.marginM * 2 * scaling)
|
||||
height: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * scaling) : Math.round(Style.capsuleHeight * scaling)
|
||||
radius: Math.round(Style.radiusM * scaling)
|
||||
color: Color.mSurfaceVariant
|
||||
|
||||
Item {
|
||||
id: mainContainer
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Style.marginS * scaling
|
||||
anchors.rightMargin: Style.marginS * scaling
|
||||
anchors.leftMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
|
||||
anchors.rightMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
|
||||
clip: true
|
||||
|
||||
// Horizontal layout for top/bottom bars
|
||||
RowLayout {
|
||||
id: contentLayout
|
||||
id: horizontalLayout
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginS * scaling
|
||||
spacing: 2 * scaling
|
||||
visible: barPosition === "top" || barPosition === "bottom"
|
||||
|
||||
// Window icon
|
||||
Item {
|
||||
Layout.preferredWidth: Style.fontSizeL * scaling * 1.2
|
||||
Layout.preferredHeight: Style.fontSizeL * scaling * 1.2
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 0.5 * scaling
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 0.5 * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: getTitle() !== "" && showIcon
|
||||
|
||||
@@ -110,16 +164,28 @@ RowLayout {
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
visible: source !== ""
|
||||
|
||||
// Handle loading errors gracefully
|
||||
onStatusChanged: {
|
||||
if (status === Image.Error) {
|
||||
Logger.warn("ActiveWindow", "Failed to load icon:", source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
id: titleText
|
||||
Layout.preferredWidth: {
|
||||
if (mouseArea.containsMouse) {
|
||||
return Math.round(Math.min(fullTitleMetrics.contentWidth, root.maxWidth * scaling))
|
||||
} else {
|
||||
return Math.round(Math.min(fullTitleMetrics.contentWidth, root.minWidth * scaling))
|
||||
try {
|
||||
if (mouseArea.containsMouse) {
|
||||
return Math.round(Math.min(fullTitleMetrics.contentWidth, root.maxWidth * scaling))
|
||||
} else {
|
||||
return Math.round(Math.min(fullTitleMetrics.contentWidth, 80 * scaling)) // Limited width for horizontal bars
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.warn("ActiveWindow", "Error calculating width:", e)
|
||||
return 80 * scaling
|
||||
}
|
||||
}
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
@@ -141,12 +207,65 @@ RowLayout {
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical layout for left/right bars - icon only
|
||||
Item {
|
||||
id: verticalLayout
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Style.marginXS * scaling * 2
|
||||
height: parent.height - Style.marginXS * scaling * 2
|
||||
visible: barPosition === "left" || barPosition === "right"
|
||||
|
||||
// Window icon
|
||||
Item {
|
||||
width: Style.baseWidgetSize * 0.5 * scaling
|
||||
height: Style.baseWidgetSize * 0.5 * scaling
|
||||
anchors.centerIn: parent
|
||||
visible: getTitle() !== "" && showIcon
|
||||
|
||||
IconImage {
|
||||
id: windowIconVertical
|
||||
anchors.fill: parent
|
||||
source: getAppIcon()
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
visible: source !== ""
|
||||
|
||||
// Handle loading errors gracefully
|
||||
onStatusChanged: {
|
||||
if (status === Image.Error) {
|
||||
Logger.warn("ActiveWindow", "Failed to load icon:", source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse area for hover detection
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: {
|
||||
if (barPosition === "left" || barPosition === "right") {
|
||||
tooltip.show()
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
if (barPosition === "left" || barPosition === "right") {
|
||||
tooltip.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hover tooltip with full title (only for vertical bars)
|
||||
NTooltip {
|
||||
id: tooltip
|
||||
target: verticalLayout
|
||||
text: getTitle()
|
||||
positionLeft: barPosition === "right"
|
||||
positionRight: barPosition === "left"
|
||||
delay: 500
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,10 +273,20 @@ RowLayout {
|
||||
Connections {
|
||||
target: CompositorService
|
||||
function onActiveWindowChanged() {
|
||||
windowIcon.source = Qt.binding(getAppIcon)
|
||||
try {
|
||||
windowIcon.source = Qt.binding(getAppIcon)
|
||||
windowIconVertical.source = Qt.binding(getAppIcon)
|
||||
} catch (e) {
|
||||
Logger.warn("ActiveWindow", "Error in onActiveWindowChanged:", e)
|
||||
}
|
||||
}
|
||||
function onWindowListChanged() {
|
||||
windowIcon.source = Qt.binding(getAppIcon)
|
||||
try {
|
||||
windowIcon.source = Qt.binding(getAppIcon)
|
||||
windowIconVertical.source = Qt.binding(getAppIcon)
|
||||
} catch (e) {
|
||||
Logger.warn("ActiveWindow", "Error in onWindowListChanged:", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,12 @@ Item {
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property string section: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
@@ -30,21 +29,18 @@ Item {
|
||||
return {}
|
||||
}
|
||||
|
||||
// Resolve settings: try user settings or defaults from BarWidgetRegistry
|
||||
readonly property bool alwaysShowPercentage: widgetSettings.alwaysShowPercentage
|
||||
!== undefined ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
readonly property real warningThreshold: widgetSettings.warningThreshold
|
||||
!== undefined ? widgetSettings.warningThreshold : widgetMetadata.warningThreshold
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode
|
||||
readonly property real warningThreshold: widgetSettings.warningThreshold !== undefined ? widgetSettings.warningThreshold : widgetMetadata.warningThreshold
|
||||
|
||||
// Test mode
|
||||
readonly property bool testMode: false
|
||||
readonly property int testPercent: 90
|
||||
readonly property int testPercent: 100
|
||||
readonly property bool testCharging: false
|
||||
|
||||
// Main properties
|
||||
readonly property var battery: UPower.displayDevice
|
||||
readonly property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery
|
||||
&& battery.isPresent)
|
||||
readonly property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery && battery.isPresent)
|
||||
readonly property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0)
|
||||
readonly property bool charging: testMode ? testCharging : (isReady ? battery.state === UPowerDeviceState.Charging : false)
|
||||
property bool hasNotifiedLowBattery: false
|
||||
@@ -89,11 +85,12 @@ Item {
|
||||
id: pill
|
||||
|
||||
rightOpen: BarWidgetRegistry.getNPillDirection(root)
|
||||
icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent,
|
||||
charging, isReady)
|
||||
text: (isReady || testMode) ? Math.round(percent) + "%" : "-"
|
||||
icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent, charging, isReady)
|
||||
text: (isReady || testMode) ? Math.round(percent) : "-"
|
||||
suffix: "%"
|
||||
autoHide: false
|
||||
forceOpen: isReady && (testMode || battery.isLaptopBattery) && alwaysShowPercentage
|
||||
forceOpen: isReady && (testMode || battery.isLaptopBattery) && displayMode === "alwaysShow"
|
||||
forceClose: displayMode === "alwaysHide"
|
||||
disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery))
|
||||
tooltipText: {
|
||||
let lines = []
|
||||
@@ -113,8 +110,7 @@ Item {
|
||||
if (battery.changeRate !== undefined) {
|
||||
const rate = battery.changeRate
|
||||
if (rate > 0) {
|
||||
lines.push(charging ? "Charging rate: " + rate.toFixed(2) + " W." : "Discharging rate: " + rate.toFixed(
|
||||
2) + " W.")
|
||||
lines.push(charging ? "Charging rate: " + rate.toFixed(2) + " W." : "Discharging rate: " + rate.toFixed(2) + " W.")
|
||||
} else if (rate < 0) {
|
||||
lines.push("Discharging rate: " + Math.abs(rate).toFixed(2) + " W.")
|
||||
} else {
|
||||
|
||||
@@ -21,5 +21,6 @@ NIconButton {
|
||||
|
||||
icon: Settings.data.network.bluetoothEnabled ? "bluetooth" : "bluetooth-off"
|
||||
tooltipText: "Bluetooth devices."
|
||||
onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(screen, this)
|
||||
onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this)
|
||||
onRightClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this)
|
||||
}
|
||||
|
||||
@@ -13,13 +13,12 @@ Item {
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property string section: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
@@ -29,8 +28,8 @@ Item {
|
||||
return {}
|
||||
}
|
||||
|
||||
readonly property bool userAlwaysShowPercentage: (widgetSettings.alwaysShowPercentage
|
||||
!== undefined) ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode
|
||||
|
||||
// Used to avoid opening the pill on Quickshell startup
|
||||
property bool firstBrightnessReceived: false
|
||||
@@ -82,15 +81,16 @@ Item {
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: {
|
||||
var monitor = getMonitor()
|
||||
return monitor ? (Math.round(monitor.brightness * 100) + "%") : ""
|
||||
return monitor ? Math.round(monitor.brightness * 100) : ""
|
||||
}
|
||||
forceOpen: userAlwaysShowPercentage
|
||||
suffix: text.length > 0 ? "%" : "-"
|
||||
forceOpen: displayMode === "alwaysShow"
|
||||
forceClose: displayMode === "alwaysHide"
|
||||
tooltipText: {
|
||||
var monitor = getMonitor()
|
||||
if (!monitor)
|
||||
return ""
|
||||
return "Brightness: " + Math.round(monitor.brightness * 100) + "%\nMethod: " + monitor.method
|
||||
+ "\nLeft click for advanced settings.\nScroll up/down to change brightness."
|
||||
return "Brightness: " + Math.round(monitor.brightness * 100) + "%\nRight click for settings.\nScroll to modify brightness."
|
||||
}
|
||||
|
||||
onWheel: function (angle) {
|
||||
@@ -106,8 +106,14 @@ Item {
|
||||
|
||||
onClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Brightness
|
||||
settingsPanel.open(screen)
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
||||
settingsPanel.open()
|
||||
}
|
||||
|
||||
onRightClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
||||
settingsPanel.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
@@ -12,13 +13,12 @@ Rectangle {
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property string section: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
@@ -28,45 +28,178 @@ Rectangle {
|
||||
return {}
|
||||
}
|
||||
|
||||
// Resolve settings: try user settings or defaults from BarWidgetRegistry
|
||||
readonly property bool showDate: widgetSettings.showDate !== undefined ? widgetSettings.showDate : widgetMetadata.showDate
|
||||
readonly property bool use12h: widgetSettings.use12HourClock !== undefined ? widgetSettings.use12HourClock : widgetMetadata.use12HourClock
|
||||
readonly property bool showSeconds: widgetSettings.showSeconds !== undefined ? widgetSettings.showSeconds : widgetMetadata.showSeconds
|
||||
readonly property bool reverseDayMonth: widgetSettings.reverseDayMonth
|
||||
!== undefined ? widgetSettings.reverseDayMonth : widgetMetadata.reverseDayMonth
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
|
||||
implicitWidth: clock.width + Style.marginM * 2 * scaling
|
||||
implicitHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
radius: Math.round(Style.radiusM * scaling)
|
||||
// Resolve settings: try user settings or defaults from BarWidgetRegistry
|
||||
readonly property bool use12h: widgetSettings.use12HourClock !== undefined ? widgetSettings.use12HourClock : widgetMetadata.use12HourClock
|
||||
readonly property bool reverseDayMonth: widgetSettings.reverseDayMonth !== undefined ? widgetSettings.reverseDayMonth : widgetMetadata.reverseDayMonth
|
||||
readonly property string displayFormat: widgetSettings.displayFormat !== undefined ? widgetSettings.displayFormat : widgetMetadata.displayFormat
|
||||
|
||||
// Use compact mode for vertical bars
|
||||
readonly property bool verticalMode: barPosition === "left" || barPosition === "right"
|
||||
|
||||
implicitWidth: verticalMode ? Math.round(Style.capsuleHeight * scaling) : Math.round(layout.implicitWidth + Style.marginM * 2 * scaling)
|
||||
implicitHeight: verticalMode ? Math.round(Style.capsuleHeight * 2.5 * scaling) : Math.round(Style.baseWidgetSize * 0.8 * scaling) // Match NPill
|
||||
|
||||
radius: Math.round(Style.radiusS * scaling)
|
||||
color: Color.mSurfaceVariant
|
||||
|
||||
// Clock Icon with attached calendar
|
||||
NText {
|
||||
id: clock
|
||||
text: {
|
||||
const now = Time.date
|
||||
const timeFormat = use12h ? (showSeconds ? "h:mm:ss AP" : "h:mm AP") : (showSeconds ? "HH:mm:ss" : "HH:mm")
|
||||
const timeString = Qt.formatDateTime(now, timeFormat)
|
||||
Item {
|
||||
id: clockContainer
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXS * scaling
|
||||
|
||||
if (showDate) {
|
||||
let dayName = now.toLocaleDateString(Qt.locale(), "ddd")
|
||||
dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1)
|
||||
let day = now.getDate()
|
||||
let month = now.toLocaleDateString(Qt.locale(), "MMM")
|
||||
return timeString + " - " + (reverseDayMonth ? `${dayName}, ${month} ${day}` : `${dayName}, ${day} ${month}`)
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
anchors.centerIn: parent
|
||||
spacing: verticalMode ? -2 * scaling : -3 * scaling
|
||||
|
||||
// Compact mode for vertical bars - Time section (HH, MM)
|
||||
Repeater {
|
||||
model: verticalMode ? 2 : 1
|
||||
NText {
|
||||
readonly property bool showSeconds: (displayFormat === "time-seconds")
|
||||
readonly property bool inlineDate: (displayFormat === "time-date")
|
||||
readonly property var now: Time.date
|
||||
|
||||
text: {
|
||||
if (verticalMode) {
|
||||
// Compact mode: time section (first 2 lines)
|
||||
switch (index) {
|
||||
case 0:
|
||||
// Hours
|
||||
if (use12h) {
|
||||
const hours = now.getHours()
|
||||
const displayHours = hours === 0 ? 12 : (hours > 12 ? hours - 12 : hours)
|
||||
return displayHours.toString().padStart(2, '0')
|
||||
} else {
|
||||
return now.getHours().toString().padStart(2, '0')
|
||||
}
|
||||
case 1:
|
||||
// Minutes
|
||||
return now.getMinutes().toString().padStart(2, '0')
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
} else {
|
||||
// Normal mode: single line with time
|
||||
let timeStr = ""
|
||||
|
||||
if (use12h) {
|
||||
// 12-hour format with proper padding and consistent spacing
|
||||
const hours = now.getHours()
|
||||
const displayHours = hours === 0 ? 12 : (hours > 12 ? hours - 12 : hours)
|
||||
const paddedHours = displayHours.toString().padStart(2, '0')
|
||||
const minutes = now.getMinutes().toString().padStart(2, '0')
|
||||
const ampm = hours < 12 ? 'AM' : 'PM'
|
||||
|
||||
if (showSeconds) {
|
||||
const seconds = now.getSeconds().toString().padStart(2, '0')
|
||||
timeStr = `${paddedHours}:${minutes}:${seconds} ${ampm}`
|
||||
} else {
|
||||
timeStr = `${paddedHours}:${minutes} ${ampm}`
|
||||
}
|
||||
} else {
|
||||
// 24-hour format with padding
|
||||
const hours = now.getHours().toString().padStart(2, '0')
|
||||
const minutes = now.getMinutes().toString().padStart(2, '0')
|
||||
|
||||
if (showSeconds) {
|
||||
const seconds = now.getSeconds().toString().padStart(2, '0')
|
||||
timeStr = `${hours}:${minutes}:${seconds}`
|
||||
} else {
|
||||
timeStr = `${hours}:${minutes}`
|
||||
}
|
||||
}
|
||||
|
||||
// Add inline date if needed
|
||||
if (inlineDate) {
|
||||
let dayName = now.toLocaleDateString(Qt.locale(), "ddd")
|
||||
dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1)
|
||||
const day = now.getDate().toString().padStart(2, '0')
|
||||
let month = now.toLocaleDateString(Qt.locale(), "MMM")
|
||||
timeStr += " - " + (reverseDayMonth ? `${dayName}, ${month} ${day}` : `${dayName}, ${day} ${month}`)
|
||||
}
|
||||
|
||||
return timeStr
|
||||
}
|
||||
}
|
||||
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: verticalMode ? Style.fontSizeXXS * scaling : Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mPrimary
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
// Separator line for compact mode (between time and date)
|
||||
Rectangle {
|
||||
visible: verticalMode
|
||||
Layout.preferredWidth: 20 * scaling
|
||||
Layout.preferredHeight: 2 * scaling
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: 3 * scaling
|
||||
Layout.bottomMargin: 3 * scaling
|
||||
color: Color.mPrimary
|
||||
opacity: 0.3
|
||||
radius: 1 * scaling
|
||||
}
|
||||
|
||||
// Compact mode for vertical bars - Date section (DD, MM)
|
||||
Repeater {
|
||||
model: verticalMode ? 2 : 0
|
||||
NText {
|
||||
readonly property var now: Time.date
|
||||
|
||||
text: {
|
||||
if (verticalMode) {
|
||||
// Compact mode: date section (last 2 lines)
|
||||
switch (index) {
|
||||
case 0:
|
||||
// Day
|
||||
return now.getDate().toString().padStart(2, '0')
|
||||
case 1:
|
||||
// Month
|
||||
return (now.getMonth() + 1).toString().padStart(2, '0')
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
font.pointSize: Style.fontSizeXXS * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mPrimary
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
// Second line for normal mode (date)
|
||||
NText {
|
||||
visible: !verticalMode && (displayFormat === "time-date-short")
|
||||
text: {
|
||||
const now = Time.date
|
||||
const day = now.getDate().toString().padStart(2, '0')
|
||||
const month = (now.getMonth() + 1).toString().padStart(2, '0')
|
||||
return reverseDayMonth ? `${month}/${day}` : `${day}/${month}`
|
||||
}
|
||||
|
||||
// Enable fixed-width font for consistent spacing
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXXS * scaling
|
||||
font.weight: Style.fontWeightRegular
|
||||
color: Color.mPrimary
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
}
|
||||
return timeString
|
||||
}
|
||||
anchors.centerIn: parent
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NTooltip {
|
||||
id: tooltip
|
||||
text: `${Time.formatDate(reverseDayMonth)}.`
|
||||
target: clock
|
||||
target: clockContainer
|
||||
positionAbove: Settings.data.bar.position === "bottom"
|
||||
}
|
||||
|
||||
@@ -85,7 +218,7 @@ Rectangle {
|
||||
}
|
||||
onClicked: {
|
||||
tooltip.hide()
|
||||
PanelService.getPanel("calendarPanel")?.toggle(screen, this)
|
||||
PanelService.getPanel("calendarPanel")?.toggle(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.SettingsPanel
|
||||
|
||||
NIconButton {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Widget properties passed from Bar.qml
|
||||
@@ -15,13 +16,12 @@ NIconButton {
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property string section: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
@@ -36,33 +36,80 @@ NIconButton {
|
||||
readonly property string leftClickExec: widgetSettings.leftClickExec || widgetMetadata.leftClickExec
|
||||
readonly property string rightClickExec: widgetSettings.rightClickExec || widgetMetadata.rightClickExec
|
||||
readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec
|
||||
readonly property string textCommand: widgetSettings.textCommand !== undefined ? widgetSettings.textCommand : (widgetMetadata.textCommand || "")
|
||||
readonly property int textIntervalMs: widgetSettings.textIntervalMs !== undefined ? widgetSettings.textIntervalMs : (widgetMetadata.textIntervalMs || 3000)
|
||||
readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec)
|
||||
|
||||
enabled: hasExec
|
||||
allowClickWhenDisabled: true // we want to be able to open config with left click when its not setup properly
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
sizeRatio: 0.8
|
||||
icon: customIcon
|
||||
tooltipText: {
|
||||
if (!hasExec) {
|
||||
return "Custom Button - Configure in settings"
|
||||
} else {
|
||||
var lines = []
|
||||
if (leftClickExec !== "") {
|
||||
lines.push(`Left click: <i>${leftClickExec}</i>.`)
|
||||
implicitWidth: pill.width
|
||||
implicitHeight: pill.height
|
||||
|
||||
NPill {
|
||||
id: pill
|
||||
|
||||
rightOpen: BarWidgetRegistry.getNPillDirection(root)
|
||||
icon: customIcon
|
||||
text: _dynamicText
|
||||
autoHide: false
|
||||
forceOpen: _dynamicText !== ""
|
||||
forceClose: false
|
||||
disableOpen: true
|
||||
tooltipText: {
|
||||
if (!hasExec) {
|
||||
return "Custom Button - Configure in settings"
|
||||
} else {
|
||||
var lines = []
|
||||
if (leftClickExec !== "") {
|
||||
lines.push(`Left click: ${leftClickExec}.`)
|
||||
}
|
||||
if (rightClickExec !== "") {
|
||||
lines.push(`Right click: ${rightClickExec}.`)
|
||||
}
|
||||
if (middleClickExec !== "") {
|
||||
lines.push(`Middle click: ${middleClickExec}.`)
|
||||
}
|
||||
return lines.join("\n")
|
||||
}
|
||||
if (rightClickExec !== "") {
|
||||
lines.push(`Right click: <i>${rightClickExec}</i>.`)
|
||||
}
|
||||
if (middleClickExec !== "") {
|
||||
lines.push(`Middle click: <i>${middleClickExec}</i>.`)
|
||||
}
|
||||
return lines.join("<br/>")
|
||||
}
|
||||
|
||||
onClicked: root.onClicked()
|
||||
onRightClicked: root.onRightClicked()
|
||||
onMiddleClicked: root.onMiddleClicked()
|
||||
}
|
||||
|
||||
// Internal state for dynamic text
|
||||
property string _dynamicText: ""
|
||||
|
||||
// Periodically run the text command (if set)
|
||||
Timer {
|
||||
id: refreshTimer
|
||||
interval: Math.max(250, textIntervalMs)
|
||||
repeat: true
|
||||
running: (textCommand && textCommand.length > 0)
|
||||
triggeredOnStart: true
|
||||
onTriggered: {
|
||||
if (!textCommand || textCommand.length === 0)
|
||||
return
|
||||
if (textProc.running)
|
||||
return
|
||||
textProc.command = ["sh", "-lc", textCommand]
|
||||
textProc.running = true
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
Process {
|
||||
id: textProc
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
var out = String(stdout.text || "").trim()
|
||||
if (out.indexOf("\n") !== -1) {
|
||||
out = out.split("\n")[0]
|
||||
}
|
||||
_dynamicText = out
|
||||
}
|
||||
}
|
||||
|
||||
function onClicked() {
|
||||
if (leftClickExec) {
|
||||
Quickshell.execDetached(["sh", "-c", leftClickExec])
|
||||
Logger.log("CustomButton", `Executing command: ${leftClickExec}`)
|
||||
@@ -70,18 +117,18 @@ NIconButton {
|
||||
// No script was defined, open settings
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Bar
|
||||
settingsPanel.open(screen)
|
||||
settingsPanel.open()
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: {
|
||||
function onRightClicked() {
|
||||
if (rightClickExec) {
|
||||
Quickshell.execDetached(["sh", "-c", rightClickExec])
|
||||
Logger.log("CustomButton", `Executing command: ${rightClickExec}`)
|
||||
}
|
||||
}
|
||||
|
||||
onMiddleClicked: {
|
||||
function onMiddleClicked() {
|
||||
if (middleClickExec) {
|
||||
Quickshell.execDetached(["sh", "-c", middleClickExec])
|
||||
Logger.log("CustomButton", `Executing command: ${middleClickExec}`)
|
||||
|
||||
@@ -17,7 +17,5 @@ NIconButton {
|
||||
colorFg: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mOnPrimary
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: Settings.data.colorSchemes.darkMode = !Settings.data.colorSchemes.darkMode
|
||||
}
|
||||
|
||||
@@ -13,6 +13,25 @@ Item {
|
||||
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 displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode
|
||||
|
||||
// Use the shared service for keyboard layout
|
||||
property string currentLayout: KeyboardLayoutService.currentLayout
|
||||
|
||||
@@ -26,9 +45,10 @@ Item {
|
||||
rightOpen: BarWidgetRegistry.getNPillDirection(root)
|
||||
icon: "keyboard"
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: currentLayout
|
||||
tooltipText: "Keyboard layout: " + currentLayout
|
||||
|
||||
text: currentLayout.toUpperCase()
|
||||
tooltipText: "Keyboard layout: " + currentLayout.toUpperCase()
|
||||
forceOpen: root.displayMode === "forceOpen"
|
||||
forceClose: root.displayMode === "alwaysHide"
|
||||
onClicked: {
|
||||
|
||||
// You could open keyboard settings here if needed
|
||||
|
||||
@@ -7,7 +7,7 @@ import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
RowLayout {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
@@ -15,13 +15,12 @@ RowLayout {
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property string section: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
@@ -31,12 +30,11 @@ RowLayout {
|
||||
return {}
|
||||
}
|
||||
|
||||
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
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
|
||||
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
|
||||
|
||||
// 6% of total width
|
||||
readonly property real minWidth: Math.max(1, screen.width * 0.06)
|
||||
@@ -46,10 +44,26 @@ RowLayout {
|
||||
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "")
|
||||
}
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Style.marginS * scaling
|
||||
function calculatedVerticalHeight() {
|
||||
return Math.round(Style.baseWidgetSize * 0.8 * scaling)
|
||||
}
|
||||
|
||||
function calculatedHorizontalWidth() {
|
||||
let total = Style.marginM * 2 * scaling // internal padding
|
||||
if (showAlbumArt) {
|
||||
total += 18 * scaling + 2 * scaling // album art + spacing
|
||||
} else {
|
||||
total += Style.fontSizeL * scaling + 2 * scaling // icon + spacing
|
||||
}
|
||||
total += Math.min(fullTitleMetrics.contentWidth, maxWidth * scaling) // title text
|
||||
// Row layout handles spacing between widgets
|
||||
return total
|
||||
}
|
||||
|
||||
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) : (rowLayout.implicitWidth + Style.marginM * 2 * scaling)) : 0
|
||||
|
||||
visible: MediaService.currentPlayer !== null && MediaService.canPlay
|
||||
Layout.preferredWidth: MediaService.currentPlayer !== null && MediaService.canPlay ? implicitWidth : 0
|
||||
|
||||
// A hidden text element to safely measure the full title width
|
||||
NText {
|
||||
@@ -61,12 +75,12 @@ RowLayout {
|
||||
|
||||
Rectangle {
|
||||
id: mediaMini
|
||||
|
||||
Layout.preferredWidth: rowLayout.implicitWidth + Style.marginM * 2 * scaling
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
radius: Math.round(Style.radiusM * scaling)
|
||||
visible: root.visible
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (rowLayout.implicitWidth + Style.marginM * 2 * 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)
|
||||
color: Color.mSurfaceVariant
|
||||
|
||||
// Used to anchor the tooltip, so the tooltip does not move when the content expands
|
||||
@@ -79,8 +93,8 @@ RowLayout {
|
||||
Item {
|
||||
id: mainContainer
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Style.marginS * scaling
|
||||
anchors.rightMargin: Style.marginS * scaling
|
||||
anchors.leftMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
|
||||
anchors.rightMargin: (barPosition === "left" || barPosition === "right") ? 0 : Style.marginS * scaling
|
||||
|
||||
Loader {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -127,10 +141,12 @@ RowLayout {
|
||||
}
|
||||
}
|
||||
|
||||
// Horizontal layout for top/bottom bars
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
visible: barPosition === "top" || barPosition === "bottom"
|
||||
z: 1 // Above the visualizer
|
||||
|
||||
NIcon {
|
||||
@@ -191,6 +207,33 @@ RowLayout {
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical layout for left/right bars - icon only
|
||||
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"
|
||||
z: 1 // Above the visualizer
|
||||
|
||||
// Media icon
|
||||
Item {
|
||||
width: Style.baseWidgetSize * 0.5 * scaling
|
||||
height: Style.baseWidgetSize * 0.5 * scaling
|
||||
anchors.centerIn: parent
|
||||
visible: getTitle() !== ""
|
||||
|
||||
NIcon {
|
||||
id: mediaIconVertical
|
||||
anchors.fill: parent
|
||||
icon: MediaService.isPlaying ? "media-pause" : "media-play"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse area for hover detection
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
@@ -213,12 +256,18 @@ RowLayout {
|
||||
}
|
||||
|
||||
onEntered: {
|
||||
if (tooltip.text !== "") {
|
||||
if (barPosition === "left" || barPosition === "right") {
|
||||
tooltip.show()
|
||||
} else if (tooltip.text !== "") {
|
||||
tooltip.show()
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
tooltip.hide()
|
||||
if (barPosition === "left" || barPosition === "right") {
|
||||
tooltip.hide()
|
||||
} else {
|
||||
tooltip.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,16 +276,23 @@ RowLayout {
|
||||
NTooltip {
|
||||
id: tooltip
|
||||
text: {
|
||||
var str = ""
|
||||
if (MediaService.canGoNext) {
|
||||
str += "Right click for next.\n"
|
||||
if (barPosition === "left" || barPosition === "right") {
|
||||
return getTitle()
|
||||
} else {
|
||||
var str = ""
|
||||
if (MediaService.canGoNext) {
|
||||
str += "Right click for next.\n"
|
||||
}
|
||||
if (MediaService.canGoPrevious) {
|
||||
str += "Middle click for previous."
|
||||
}
|
||||
return str
|
||||
}
|
||||
if (MediaService.canGoPrevious) {
|
||||
str += "Middle click for previous."
|
||||
}
|
||||
return str
|
||||
}
|
||||
target: anchor
|
||||
target: (barPosition === "left" || barPosition === "right") ? verticalLayout : anchor
|
||||
positionLeft: barPosition === "right"
|
||||
positionRight: barPosition === "left"
|
||||
positionAbove: Settings.data.bar.position === "bottom"
|
||||
delay: 500
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,12 @@ Item {
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property string section: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
@@ -31,8 +30,8 @@ Item {
|
||||
return {}
|
||||
}
|
||||
|
||||
readonly property bool alwaysShowPercentage: (widgetSettings.alwaysShowPercentage
|
||||
!== undefined) ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode
|
||||
|
||||
// Used to avoid opening the pill on Quickshell startup
|
||||
property bool firstInputVolumeReceived: false
|
||||
@@ -89,14 +88,14 @@ Item {
|
||||
|
||||
NPill {
|
||||
id: pill
|
||||
|
||||
rightOpen: BarWidgetRegistry.getNPillDirection(root)
|
||||
icon: getIcon()
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: Math.floor(AudioService.inputVolume * 100) + "%"
|
||||
forceOpen: alwaysShowPercentage
|
||||
tooltipText: "Microphone: " + Math.round(AudioService.inputVolume * 100)
|
||||
+ "%\nLeft click for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute."
|
||||
text: Math.floor(AudioService.inputVolume * 100)
|
||||
suffix: "%"
|
||||
forceOpen: displayMode === "alwaysShow"
|
||||
forceClose: displayMode === "alwaysHide"
|
||||
tooltipText: "Microphone: " + Math.round(AudioService.inputVolume * 100) + "%\nLeft click to toggle mute.\nRight click for settings.\nScroll to modify volume."
|
||||
|
||||
onWheel: function (delta) {
|
||||
wheelAccumulator += delta
|
||||
@@ -109,12 +108,12 @@ Item {
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Audio
|
||||
settingsPanel.open(screen)
|
||||
AudioService.setInputMuted(!AudioService.inputMuted)
|
||||
}
|
||||
onRightClicked: {
|
||||
AudioService.setInputMuted(!AudioService.inputMuted)
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Audio
|
||||
settingsPanel.open()
|
||||
}
|
||||
onMiddleClicked: {
|
||||
Quickshell.execDetached(["pwvucontrol"])
|
||||
|
||||
@@ -15,8 +15,8 @@ NIconButton {
|
||||
property real scaling: 1.0
|
||||
|
||||
sizeRatio: 0.8
|
||||
colorBg: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? Color.mTertiary : Color.mPrimary) : Color.mSurfaceVariant
|
||||
colorFg: Settings.data.nightLight.enabled ? Color.mOnPrimary : Color.mOnSurface
|
||||
colorBg: Settings.data.nightLight.forced ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: Settings.data.nightLight.forced ? Color.mOnPrimary : Color.mOnSurface
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
|
||||
@@ -36,7 +36,7 @@ NIconButton {
|
||||
|
||||
onRightClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Brightness
|
||||
settingsPanel.open(screen)
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
||||
settingsPanel.open()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,12 @@ NIconButton {
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property string section: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
@@ -30,10 +29,8 @@ NIconButton {
|
||||
}
|
||||
return {}
|
||||
}
|
||||
readonly property bool showUnreadBadge: (widgetSettings.showUnreadBadge
|
||||
!== undefined) ? widgetSettings.showUnreadBadge : widgetMetadata.showUnreadBadge
|
||||
readonly property bool hideWhenZero: (widgetSettings.hideWhenZero
|
||||
!== undefined) ? widgetSettings.hideWhenZero : widgetMetadata.hideWhenZero
|
||||
readonly property bool showUnreadBadge: (widgetSettings.showUnreadBadge !== undefined) ? widgetSettings.showUnreadBadge : widgetMetadata.showUnreadBadge
|
||||
readonly property bool hideWhenZero: (widgetSettings.hideWhenZero !== undefined) ? widgetSettings.hideWhenZero : widgetMetadata.hideWhenZero
|
||||
|
||||
function lastSeenTs() {
|
||||
return Settings.data.notifications?.lastSeenTs || 0
|
||||
@@ -62,7 +59,7 @@ NIconButton {
|
||||
|
||||
onClicked: {
|
||||
var panel = PanelService.getPanel("notificationHistoryPanel")
|
||||
panel?.toggle(screen, this)
|
||||
panel?.toggle(this)
|
||||
Settings.data.notifications.lastSeenTs = Time.timestamp * 1000
|
||||
}
|
||||
|
||||
|
||||
@@ -19,5 +19,5 @@ NIconButton {
|
||||
colorFg: Color.mError
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
onClicked: PanelService.getPanel("powerPanel")?.toggle(screen)
|
||||
onClicked: PanelService.getPanel("powerPanel")?.toggle()
|
||||
}
|
||||
|
||||
@@ -16,6 +16,5 @@ NIconButton {
|
||||
sizeRatio: 0.8
|
||||
colorBg: Color.mPrimary
|
||||
colorFg: Color.mOnPrimary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: ScreenRecorderService.toggleRecording()
|
||||
}
|
||||
|
||||
@@ -14,13 +14,12 @@ NIconButton {
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property string section: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
@@ -30,39 +29,27 @@ NIconButton {
|
||||
return {}
|
||||
}
|
||||
|
||||
readonly property bool useDistroLogo: (widgetSettings.useDistroLogo
|
||||
!== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo
|
||||
readonly property bool useDistroLogo: (widgetSettings.useDistroLogo !== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo
|
||||
|
||||
icon: useDistroLogo ? "" : "apps"
|
||||
icon: useDistroLogo ? "" : "noctalia"
|
||||
tooltipText: "Open side panel."
|
||||
sizeRatio: 0.8
|
||||
sizeRatio: 0.85
|
||||
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorFg: Color.mOnSurface
|
||||
colorBgHover: useDistroLogo ? Color.mSurfaceVariant : Color.mTertiary
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: PanelService.getPanel("sidePanel")?.toggle(screen, this)
|
||||
onRightClicked: PanelService.getPanel("settingsPanel")?.toggle(screen)
|
||||
colorBorderHover: useDistroLogo ? Color.mTertiary : Color.transparent
|
||||
onClicked: PanelService.getPanel("sidePanel")?.toggle(this)
|
||||
onRightClicked: PanelService.getPanel("settingsPanel")?.toggle()
|
||||
|
||||
IconImage {
|
||||
id: logo
|
||||
anchors.centerIn: parent
|
||||
width: root.width * 0.6
|
||||
width: root.width * 0.85
|
||||
height: width
|
||||
source: useDistroLogo ? DistroLogoService.osLogo : ""
|
||||
visible: useDistroLogo && source !== ""
|
||||
smooth: true
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: logo
|
||||
source: logo
|
||||
//visible: logo.visible
|
||||
colorization: 1
|
||||
brightness: 1
|
||||
saturation: 1
|
||||
colorizationColor: root.hovering ? Color.mSurfaceVariant : Color.mOnSurface
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,12 @@ Item {
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property string section: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
RowLayout {
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
@@ -13,13 +13,12 @@ RowLayout {
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property string section: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
@@ -29,245 +28,419 @@ RowLayout {
|
||||
return {}
|
||||
}
|
||||
|
||||
readonly property bool showCpuUsage: (widgetSettings.showCpuUsage
|
||||
!== undefined) ? widgetSettings.showCpuUsage : widgetMetadata.showCpuUsage
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
|
||||
readonly property bool showCpuUsage: (widgetSettings.showCpuUsage !== undefined) ? widgetSettings.showCpuUsage : widgetMetadata.showCpuUsage
|
||||
readonly property bool showCpuTemp: (widgetSettings.showCpuTemp !== undefined) ? widgetSettings.showCpuTemp : widgetMetadata.showCpuTemp
|
||||
readonly property bool showMemoryUsage: (widgetSettings.showMemoryUsage
|
||||
!== undefined) ? widgetSettings.showMemoryUsage : widgetMetadata.showMemoryUsage
|
||||
readonly property bool showMemoryAsPercent: (widgetSettings.showMemoryAsPercent
|
||||
!== undefined) ? widgetSettings.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
|
||||
readonly property bool showNetworkStats: (widgetSettings.showNetworkStats
|
||||
!== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats
|
||||
readonly property bool showDiskUsage: (widgetSettings.showDiskUsage
|
||||
!== undefined) ? widgetSettings.showDiskUsage : widgetMetadata.showDiskUsage
|
||||
readonly property bool showGpuTemp: (widgetSettings.showGpuTemp !== undefined) ? widgetSettings.showGpuTemp : (widgetMetadata.showGpuTemp
|
||||
|| false)
|
||||
readonly property bool showMemoryUsage: (widgetSettings.showMemoryUsage !== undefined) ? widgetSettings.showMemoryUsage : widgetMetadata.showMemoryUsage
|
||||
readonly property bool showMemoryAsPercent: (widgetSettings.showMemoryAsPercent !== undefined) ? widgetSettings.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
|
||||
readonly property bool showNetworkStats: (widgetSettings.showNetworkStats !== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats
|
||||
readonly property bool showDiskUsage: (widgetSettings.showDiskUsage !== undefined) ? widgetSettings.showDiskUsage : widgetMetadata.showDiskUsage
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Style.marginS * scaling
|
||||
anchors.centerIn: parent
|
||||
implicitWidth: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * scaling) : Math.round(horizontalLayout.implicitWidth + Style.marginM * 2 * scaling)
|
||||
implicitHeight: (barPosition === "left" || barPosition === "right") ? Math.round(verticalLayout.implicitHeight + Style.marginM * 2 * scaling) : Math.round(Style.capsuleHeight * scaling)
|
||||
radius: Math.round(Style.radiusM * scaling)
|
||||
color: Color.mSurfaceVariant
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.preferredWidth: mainLayout.implicitWidth + Style.marginM * scaling * 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
// Compact speed formatter for vertical bar display
|
||||
function formatCompactSpeed(bytesPerSecond) {
|
||||
if (!bytesPerSecond || bytesPerSecond <= 0)
|
||||
return "0"
|
||||
const units = ["", "k", "M", "G"]
|
||||
let value = bytesPerSecond
|
||||
let unitIndex = 0
|
||||
while (value >= 1024 && unitIndex < units.length - 1) {
|
||||
value = value / 1024.0
|
||||
unitIndex++
|
||||
}
|
||||
// Promote at ~100 of current unit (e.g., 100k -> ~0.1M shown as 0.1M or 0M if rounded)
|
||||
if (unitIndex < units.length - 1 && value >= 100) {
|
||||
value = value / 1024.0
|
||||
unitIndex++
|
||||
}
|
||||
const display = (value >= 10) ? Math.round(value).toString() : value.toFixed(1)
|
||||
return display + units[unitIndex]
|
||||
}
|
||||
|
||||
radius: Math.round(Style.radiusM * scaling)
|
||||
color: Color.mSurfaceVariant
|
||||
// Horizontal layout for top/bottom bars
|
||||
RowLayout {
|
||||
id: horizontalLayout
|
||||
anchors.centerIn: parent
|
||||
anchors.leftMargin: Style.marginM * scaling
|
||||
anchors.rightMargin: Style.marginM * scaling
|
||||
spacing: Style.marginXS * scaling
|
||||
visible: barPosition === "top" || barPosition === "bottom"
|
||||
|
||||
RowLayout {
|
||||
id: mainLayout
|
||||
anchors.centerIn: parent // Better centering than margins
|
||||
width: parent.width - Style.marginM * scaling * 2
|
||||
spacing: Style.marginS * scaling
|
||||
// CPU Usage Component
|
||||
Item {
|
||||
Layout.preferredWidth: cpuUsageRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showCpuUsage
|
||||
|
||||
// CPU Usage Component
|
||||
Item {
|
||||
Layout.preferredWidth: cpuUsageRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showCpuUsage
|
||||
RowLayout {
|
||||
id: cpuUsageRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXXS * scaling
|
||||
|
||||
RowLayout {
|
||||
id: cpuUsageRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
NIcon {
|
||||
icon: "cpu-usage"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NIcon {
|
||||
icon: "cpu-usage"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `${SystemStatService.cpuUsage}%`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
NText {
|
||||
text: `${SystemStatService.cpuUsage}%`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CPU Temperature Component
|
||||
Item {
|
||||
Layout.preferredWidth: cpuTempRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showCpuTemp
|
||||
// CPU Temperature Component
|
||||
Item {
|
||||
Layout.preferredWidth: cpuTempRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showCpuTemp
|
||||
|
||||
RowLayout {
|
||||
id: cpuTempRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
RowLayout {
|
||||
id: cpuTempRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXXS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "cpu-temperature"
|
||||
// Fire is so tall, we need to make it smaller
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
NIcon {
|
||||
icon: "cpu-temperature"
|
||||
// Fire is so tall, we need to make it smaller
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `${SystemStatService.cpuTemp}°C`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
NText {
|
||||
text: `${SystemStatService.cpuTemp}°C`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GPU Temperature Component
|
||||
Item {
|
||||
Layout.preferredWidth: gpuTempRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showGpuTemp
|
||||
// Memory Usage Component
|
||||
Item {
|
||||
Layout.preferredWidth: memoryUsageRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showMemoryUsage
|
||||
|
||||
RowLayout {
|
||||
id: gpuTempRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
RowLayout {
|
||||
id: memoryUsageRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXXS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "gpu-temperature"
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
NIcon {
|
||||
icon: "memory"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `${SystemStatService.gpuTemp}°C`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
NText {
|
||||
text: showMemoryAsPercent ? `${SystemStatService.memPercent}%` : `${SystemStatService.memGb}G`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Memory Usage Component
|
||||
Item {
|
||||
Layout.preferredWidth: memoryUsageRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showMemoryUsage
|
||||
// Network Download Speed Component
|
||||
Item {
|
||||
Layout.preferredWidth: networkDownloadRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showNetworkStats
|
||||
|
||||
RowLayout {
|
||||
id: memoryUsageRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
RowLayout {
|
||||
id: networkDownloadRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "memory"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
NIcon {
|
||||
icon: "download-speed"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: showMemoryAsPercent ? `${SystemStatService.memPercent}%` : `${SystemStatService.memGb}G`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
NText {
|
||||
text: SystemStatService.formatSpeed(SystemStatService.rxSpeed)
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Network Download Speed Component
|
||||
Item {
|
||||
Layout.preferredWidth: networkDownloadRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showNetworkStats
|
||||
// Network Upload Speed Component
|
||||
Item {
|
||||
Layout.preferredWidth: networkUploadRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showNetworkStats
|
||||
|
||||
RowLayout {
|
||||
id: networkDownloadRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
RowLayout {
|
||||
id: networkUploadRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "download-speed"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
NIcon {
|
||||
icon: "upload-speed"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: SystemStatService.formatSpeed(SystemStatService.rxSpeed)
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
NText {
|
||||
text: SystemStatService.formatSpeed(SystemStatService.txSpeed)
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Network Upload Speed Component
|
||||
Item {
|
||||
Layout.preferredWidth: networkUploadRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showNetworkStats
|
||||
// Disk Usage Component (primary drive)
|
||||
Item {
|
||||
Layout.preferredWidth: diskUsageRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showDiskUsage
|
||||
|
||||
RowLayout {
|
||||
id: networkUploadRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
RowLayout {
|
||||
id: diskUsageRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "upload-speed"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
NIcon {
|
||||
icon: "storage"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: SystemStatService.formatSpeed(SystemStatService.txSpeed)
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
NText {
|
||||
text: `${SystemStatService.diskPercent}%`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disk Usage Component (primary drive)
|
||||
Item {
|
||||
Layout.preferredWidth: diskUsageRow.implicitWidth
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: showDiskUsage
|
||||
// Vertical layout for left/right bars
|
||||
ColumnLayout {
|
||||
id: verticalLayout
|
||||
anchors.centerIn: parent
|
||||
anchors.topMargin: Style.marginS * scaling
|
||||
anchors.bottomMargin: Style.marginS * scaling
|
||||
width: Math.round(28 * scaling)
|
||||
spacing: Style.marginS * scaling
|
||||
visible: barPosition === "left" || barPosition === "right"
|
||||
|
||||
RowLayout {
|
||||
id: diskUsageRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXS * scaling
|
||||
// CPU Usage Component
|
||||
Item {
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.preferredWidth: Math.round(28 * scaling)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: showCpuUsage
|
||||
|
||||
NIcon {
|
||||
icon: "storage"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
Column {
|
||||
id: cpuUsageRowVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXXS * scaling
|
||||
|
||||
NText {
|
||||
text: `${SystemStatService.diskPercent}%`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
NText {
|
||||
text: `${Math.round(SystemStatService.cpuUsage)}%`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NIcon {
|
||||
icon: "cpu-usage"
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CPU Temperature Component
|
||||
Item {
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.preferredWidth: Math.round(28 * scaling)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: showCpuTemp
|
||||
|
||||
Column {
|
||||
id: cpuTempRowVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXXS * scaling
|
||||
|
||||
NText {
|
||||
text: `${SystemStatService.cpuTemp}°`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NIcon {
|
||||
icon: "cpu-temperature"
|
||||
// Fire is so tall, we need to make it smaller
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Memory Usage Component
|
||||
Item {
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.preferredWidth: Math.round(28 * scaling)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: showMemoryUsage
|
||||
|
||||
Column {
|
||||
id: memoryUsageRowVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXXS * scaling
|
||||
|
||||
NText {
|
||||
text: showMemoryAsPercent ? `${SystemStatService.memPercent}%` : `${Math.round(SystemStatService.memGb)}G`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NIcon {
|
||||
icon: "memory"
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Network Download Speed Component
|
||||
Item {
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.preferredWidth: Math.round(28 * scaling)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: showNetworkStats
|
||||
|
||||
Column {
|
||||
id: networkDownloadRowVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXXS * scaling
|
||||
|
||||
NText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: formatCompactSpeed(SystemStatService.rxSpeed)
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NIcon {
|
||||
icon: "download-speed"
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Network Upload Speed Component
|
||||
Item {
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.preferredWidth: Math.round(28 * scaling)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: showNetworkStats
|
||||
|
||||
Column {
|
||||
id: networkUploadRowVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXXS * scaling
|
||||
|
||||
NText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: formatCompactSpeed(SystemStatService.txSpeed)
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NIcon {
|
||||
icon: "upload-speed"
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disk Usage Component (primary drive)
|
||||
Item {
|
||||
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
Layout.preferredWidth: Math.round(28 * scaling)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: showDiskUsage
|
||||
|
||||
ColumnLayout {
|
||||
id: diskUsageRowVertical
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginXXS * scaling
|
||||
|
||||
NText {
|
||||
text: `${SystemStatService.diskPercent}%`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeXXS * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NIcon {
|
||||
icon: "storage"
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,36 +17,37 @@ Rectangle {
|
||||
property real scaling: 1.0
|
||||
|
||||
readonly property real itemSize: 24 * scaling
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
|
||||
|
||||
function onLoaded() {
|
||||
// When the widget is fully initialized with its props
|
||||
// set the screen for the trayMenu
|
||||
// When the widget is fully initialized with its props set the screen for the trayMenu
|
||||
if (trayMenu.item) {
|
||||
trayMenu.item.screen = screen
|
||||
}
|
||||
}
|
||||
|
||||
visible: SystemTray.items.values.length > 0
|
||||
implicitWidth: trayLayout.implicitWidth + Style.marginM * scaling * 2
|
||||
implicitHeight: Math.round(Style.capsuleHeight * scaling)
|
||||
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)
|
||||
color: Color.mSurfaceVariant
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
RowLayout {
|
||||
id: trayLayout
|
||||
Flow {
|
||||
id: trayFlow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginS * scaling
|
||||
flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
model: SystemTray.items
|
||||
|
||||
delegate: Item {
|
||||
Layout.preferredWidth: itemSize
|
||||
Layout.preferredHeight: itemSize
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
width: itemSize
|
||||
height: itemSize
|
||||
visible: modelData
|
||||
|
||||
IconImage {
|
||||
@@ -111,9 +112,21 @@ Rectangle {
|
||||
if (modelData.hasMenu && modelData.menu && trayMenu.item) {
|
||||
trayPanel.open()
|
||||
|
||||
// Anchor the menu to the tray icon item (parent) and position it below the icon
|
||||
const menuX = (width / 2) - (trayMenu.item.width / 2)
|
||||
const menuY = Math.round(Style.barHeight * scaling)
|
||||
// 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
|
||||
} else {
|
||||
// For horizontal bars: center horizontally and position below
|
||||
menuX = (width / 2) - (trayMenu.item.width / 2)
|
||||
menuY = Math.round(Style.barHeight * scaling)
|
||||
}
|
||||
trayMenu.item.menu = modelData.menu
|
||||
trayMenu.item.showAt(parent, menuX, menuY)
|
||||
} else {
|
||||
|
||||
@@ -15,13 +15,12 @@ Item {
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property string section: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
@@ -31,8 +30,8 @@ Item {
|
||||
return {}
|
||||
}
|
||||
|
||||
readonly property bool alwaysShowPercentage: (widgetSettings.alwaysShowPercentage
|
||||
!== undefined) ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode
|
||||
|
||||
// Used to avoid opening the pill on Quickshell startup
|
||||
property bool firstVolumeReceived: false
|
||||
@@ -78,10 +77,11 @@ Item {
|
||||
rightOpen: BarWidgetRegistry.getNPillDirection(root)
|
||||
icon: getIcon()
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: Math.floor(AudioService.volume * 100) + "%"
|
||||
forceOpen: alwaysShowPercentage
|
||||
tooltipText: "Volume: " + Math.round(AudioService.volume * 100)
|
||||
+ "%\nLeft click for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute."
|
||||
text: Math.floor(AudioService.volume * 100)
|
||||
suffix: "%"
|
||||
forceOpen: displayMode === "alwaysShow"
|
||||
forceClose: displayMode === "alwaysHide"
|
||||
tooltipText: "Volume: " + Math.round(AudioService.volume * 100) + "%\nLeft click to toggle mute.\nRight click for settings.\nScroll to modify volume."
|
||||
|
||||
onWheel: function (delta) {
|
||||
wheelAccumulator += delta
|
||||
@@ -94,12 +94,12 @@ Item {
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Audio
|
||||
settingsPanel.open(screen)
|
||||
AudioService.setOutputMuted(!AudioService.muted)
|
||||
}
|
||||
onRightClicked: {
|
||||
AudioService.setMuted(!AudioService.muted)
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Audio
|
||||
settingsPanel.open()
|
||||
}
|
||||
onMiddleClicked: {
|
||||
Quickshell.execDetached(["pwvucontrol"])
|
||||
|
||||
@@ -41,5 +41,6 @@ NIconButton {
|
||||
}
|
||||
}
|
||||
tooltipText: "Manage Wi-Fi."
|
||||
onClicked: PanelService.getPanel("wifiPanel")?.toggle(screen, this)
|
||||
onClicked: PanelService.getPanel("wifiPanel")?.toggle(this)
|
||||
onRightClicked: PanelService.getPanel("wifiPanel")?.toggle(this)
|
||||
}
|
||||
|
||||
@@ -16,13 +16,12 @@ Item {
|
||||
|
||||
// Widget properties passed from Bar.qml for per-instance settings
|
||||
property string widgetId: ""
|
||||
property string barSection: ""
|
||||
property string section: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
var section = barSection.replace("Section", "").toLowerCase()
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
@@ -32,7 +31,10 @@ Item {
|
||||
return {}
|
||||
}
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
|
||||
readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode
|
||||
readonly property bool hideUnoccupied: (widgetSettings.hideUnoccupied !== undefined) ? widgetSettings.hideUnoccupied : widgetMetadata.hideUnoccupied
|
||||
|
||||
property bool isDestroying: false
|
||||
property bool hovered: false
|
||||
@@ -47,17 +49,8 @@ Item {
|
||||
|
||||
signal workspaceChanged(int workspaceId, color accentColor)
|
||||
|
||||
implicitHeight: Math.round(Style.barHeight * scaling)
|
||||
implicitWidth: {
|
||||
let total = 0
|
||||
for (var i = 0; i < localWorkspaces.count; i++) {
|
||||
const ws = localWorkspaces.get(i)
|
||||
total += calculatedWsWidth(ws)
|
||||
}
|
||||
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills
|
||||
total += horizontalPadding * 2
|
||||
return total
|
||||
}
|
||||
implicitHeight: (barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling)
|
||||
implicitWidth: (barPosition === "left" || barPosition === "right") ? Math.round(Style.barHeight * scaling) : calculatedHorizontalWidth()
|
||||
|
||||
function calculatedWsWidth(ws) {
|
||||
if (ws.isFocused)
|
||||
@@ -68,6 +61,37 @@ Item {
|
||||
return Math.round(20 * scaling)
|
||||
}
|
||||
|
||||
function calculatedWsHeight(ws) {
|
||||
if (ws.isFocused)
|
||||
return Math.round(44 * scaling)
|
||||
else if (ws.isActive)
|
||||
return Math.round(28 * scaling)
|
||||
else
|
||||
return Math.round(20 * scaling)
|
||||
}
|
||||
|
||||
function calculatedVerticalHeight() {
|
||||
let total = 0
|
||||
for (var i = 0; i < localWorkspaces.count; i++) {
|
||||
const ws = localWorkspaces.get(i)
|
||||
total += calculatedWsHeight(ws)
|
||||
}
|
||||
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills
|
||||
total += horizontalPadding * 2
|
||||
return total
|
||||
}
|
||||
|
||||
function calculatedHorizontalWidth() {
|
||||
let total = 0
|
||||
for (var i = 0; i < localWorkspaces.count; i++) {
|
||||
const ws = localWorkspaces.get(i)
|
||||
total += calculatedWsWidth(ws)
|
||||
}
|
||||
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills
|
||||
total += horizontalPadding * 2
|
||||
return total
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
refreshWorkspaces()
|
||||
}
|
||||
@@ -77,6 +101,7 @@ Item {
|
||||
}
|
||||
|
||||
onScreenChanged: refreshWorkspaces()
|
||||
onHideUnoccupiedChanged: refreshWorkspaces()
|
||||
|
||||
Connections {
|
||||
target: WorkspaceService
|
||||
@@ -91,11 +116,15 @@ Item {
|
||||
for (var i = 0; i < WorkspaceService.workspaces.count; i++) {
|
||||
const ws = WorkspaceService.workspaces.get(i)
|
||||
if (ws.output.toLowerCase() === screen.name.toLowerCase()) {
|
||||
if (hideUnoccupied && !ws.isOccupied && !ws.isFocused) {
|
||||
continue
|
||||
}
|
||||
localWorkspaces.append(ws)
|
||||
}
|
||||
}
|
||||
}
|
||||
workspaceRepeater.model = localWorkspaces
|
||||
workspaceRepeaterHorizontal.model = localWorkspaces
|
||||
workspaceRepeaterVertical.model = localWorkspaces
|
||||
updateWorkspaceFocus()
|
||||
}
|
||||
|
||||
@@ -144,9 +173,8 @@ Item {
|
||||
|
||||
Rectangle {
|
||||
id: workspaceBackground
|
||||
width: parent.width
|
||||
|
||||
height: Math.round(Style.capsuleHeight * scaling)
|
||||
width: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * scaling) : parent.width
|
||||
height: (barPosition === "left" || barPosition === "right") ? parent.height : Math.round(Style.capsuleHeight * scaling)
|
||||
radius: Math.round(Style.radiusM * scaling)
|
||||
color: Color.mSurfaceVariant
|
||||
|
||||
@@ -154,14 +182,17 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
// Horizontal layout for top/bottom bars
|
||||
Row {
|
||||
id: pillRow
|
||||
spacing: spacingBetweenPills
|
||||
anchors.verticalCenter: workspaceBackground.verticalCenter
|
||||
width: root.width - horizontalPadding * 2
|
||||
x: horizontalPadding
|
||||
visible: barPosition === "top" || barPosition === "bottom"
|
||||
|
||||
Repeater {
|
||||
id: workspaceRepeater
|
||||
id: workspaceRepeaterHorizontal
|
||||
model: localWorkspaces
|
||||
Item {
|
||||
id: workspacePillContainer
|
||||
@@ -197,8 +228,6 @@ Item {
|
||||
return Color.mOnError
|
||||
if (model.isActive || model.isOccupied)
|
||||
return Color.mOnSecondary
|
||||
if (model.isUrgent)
|
||||
return Color.mOnError
|
||||
|
||||
return Color.mOnSurface
|
||||
}
|
||||
@@ -214,8 +243,6 @@ Item {
|
||||
return Color.mError
|
||||
if (model.isActive || model.isOccupied)
|
||||
return Color.mSecondary
|
||||
if (model.isUrgent)
|
||||
return Color.mError
|
||||
|
||||
return Color.mOutline
|
||||
}
|
||||
@@ -299,4 +326,149 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical layout for left/right bars
|
||||
Column {
|
||||
id: pillColumn
|
||||
spacing: spacingBetweenPills
|
||||
anchors.horizontalCenter: workspaceBackground.horizontalCenter
|
||||
height: root.height - horizontalPadding * 2
|
||||
y: horizontalPadding
|
||||
visible: barPosition === "left" || barPosition === "right"
|
||||
|
||||
Repeater {
|
||||
id: workspaceRepeaterVertical
|
||||
model: localWorkspaces
|
||||
Item {
|
||||
id: workspacePillContainerVertical
|
||||
width: (labelMode !== "none") ? Math.round(18 * scaling) : Math.round(14 * scaling)
|
||||
height: root.calculatedWsHeight(model)
|
||||
|
||||
Rectangle {
|
||||
id: pillVertical
|
||||
anchors.fill: parent
|
||||
|
||||
Loader {
|
||||
active: (labelMode !== "none")
|
||||
sourceComponent: Component {
|
||||
Text {
|
||||
x: (pillVertical.width - width) / 2
|
||||
y: (pillVertical.height - height) / 2 + (height - contentHeight) / 2
|
||||
text: {
|
||||
if (labelMode === "name" && model.name && model.name.length > 0) {
|
||||
return model.name.substring(0, 2)
|
||||
} else {
|
||||
return model.idx.toString()
|
||||
}
|
||||
}
|
||||
font.pointSize: model.isFocused ? Style.fontSizeXS * scaling : Style.fontSizeXXS * scaling
|
||||
font.capitalization: Font.AllUppercase
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.weight: Style.fontWeightBold
|
||||
wrapMode: Text.Wrap
|
||||
color: {
|
||||
if (model.isFocused)
|
||||
return Color.mOnPrimary
|
||||
if (model.isUrgent)
|
||||
return Color.mOnError
|
||||
if (model.isActive || model.isOccupied)
|
||||
return Color.mOnSecondary
|
||||
|
||||
return Color.mOnSurface
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
radius: width * 0.5
|
||||
color: {
|
||||
if (model.isFocused)
|
||||
return Color.mPrimary
|
||||
if (model.isUrgent)
|
||||
return Color.mError
|
||||
if (model.isActive || model.isOccupied)
|
||||
return Color.mSecondary
|
||||
|
||||
return Color.mOutline
|
||||
}
|
||||
scale: model.isFocused ? 1.0 : 0.9
|
||||
z: 0
|
||||
|
||||
MouseArea {
|
||||
id: pillMouseAreaVertical
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
WorkspaceService.switchToWorkspace(model.idx)
|
||||
}
|
||||
hoverEnabled: true
|
||||
}
|
||||
// Material 3-inspired smooth animation for width, height, scale, color, opacity, and radius
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutBack
|
||||
}
|
||||
}
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutBack
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutBack
|
||||
}
|
||||
}
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutCubic
|
||||
}
|
||||
}
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.InOutCubic
|
||||
}
|
||||
}
|
||||
Behavior on radius {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutBack
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutBack
|
||||
}
|
||||
}
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutBack
|
||||
}
|
||||
}
|
||||
// Burst effect overlay for focused pill (smaller outline)
|
||||
Rectangle {
|
||||
id: pillBurstVertical
|
||||
anchors.centerIn: workspacePillContainerVertical
|
||||
width: workspacePillContainerVertical.width + 18 * root.masterProgress * scale
|
||||
height: workspacePillContainerVertical.height + 18 * root.masterProgress * scale
|
||||
radius: width / 2
|
||||
color: Color.transparent
|
||||
border.color: root.effectColor
|
||||
border.width: Math.max(1, Math.round((2 + 6 * (1.0 - root.masterProgress)) * scaling))
|
||||
opacity: root.effectsActive && model.isFocused ? (1.0 - root.masterProgress) * 0.7 : 0
|
||||
visible: root.effectsActive && model.isFocused
|
||||
z: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,8 +116,7 @@ ColumnLayout {
|
||||
|
||||
NText {
|
||||
visible: modelData.signalStrength > 0 && !modelData.pairing && !modelData.blocked
|
||||
text: (modelData.signalStrength !== undefined
|
||||
&& modelData.signalStrength > 0) ? modelData.signalStrength + "%" : ""
|
||||
text: (modelData.signalStrength !== undefined && modelData.signalStrength > 0) ? modelData.signalStrength + "%" : ""
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: getContentColor(Color.mOnSurface)
|
||||
}
|
||||
|
||||
@@ -11,8 +11,9 @@ import qs.Widgets
|
||||
NPanel {
|
||||
id: root
|
||||
|
||||
panelWidth: 380 * scaling
|
||||
panelHeight: 500 * scaling
|
||||
preferredWidth: 380
|
||||
preferredHeight: 500
|
||||
panelKeyboardFocus: true
|
||||
|
||||
panelContent: Rectangle {
|
||||
color: Color.transparent
|
||||
@@ -42,7 +43,7 @@ NPanel {
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: wifiSwitch
|
||||
id: bluetoothSwitch
|
||||
checked: Settings.data.network.bluetoothEnabled
|
||||
onToggled: checked => BluetoothService.setBluetoothEnabled(checked)
|
||||
baseSize: Style.baseWidgetSize * 0.65 * scaling
|
||||
@@ -62,7 +63,7 @@ NPanel {
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: "Close"
|
||||
tooltipText: "Close."
|
||||
sizeRatio: 0.8
|
||||
onClicked: {
|
||||
root.close()
|
||||
@@ -75,7 +76,7 @@ NPanel {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: !Settings.data.network.bluetoothEnabled
|
||||
visible: !(BluetoothService.adapter && BluetoothService.adapter.enabled)
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: Color.transparent
|
||||
@@ -108,12 +109,12 @@ NPanel {
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
NScrollView {
|
||||
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
clip: true
|
||||
contentWidth: availableWidth
|
||||
|
||||
@@ -142,8 +143,7 @@ NPanel {
|
||||
property var items: {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||
return []
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.connected
|
||||
&& (dev.paired || dev.trusted))
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.connected && (dev.paired || dev.trusted))
|
||||
return BluetoothService.sortDevices(filtered)
|
||||
}
|
||||
model: items
|
||||
@@ -175,10 +175,7 @@ NPanel {
|
||||
}
|
||||
|
||||
var availableCount = Bluetooth.devices.values.filter(dev => {
|
||||
return dev && !dev.paired && !dev.pairing
|
||||
&& !dev.blocked
|
||||
&& (dev.signalStrength === undefined
|
||||
|| dev.signalStrength > 0)
|
||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0)
|
||||
}).length
|
||||
return (availableCount === 0)
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ import qs.Widgets
|
||||
NPanel {
|
||||
id: root
|
||||
|
||||
panelWidth: 340 * scaling
|
||||
panelHeight: 320 * scaling
|
||||
panelAnchorRight: true
|
||||
preferredWidth: 340
|
||||
preferredHeight: 320
|
||||
panelAnchorRight: Settings.data.bar.position === "right"
|
||||
|
||||
// Main Column
|
||||
panelContent: ColumnLayout {
|
||||
|
||||
@@ -12,10 +12,10 @@ import qs.Widgets
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
delegate: Loader {
|
||||
|
||||
delegate: Item {
|
||||
required property ShellScreen modelData
|
||||
property real scaling: ScalingService.getScreenScale(modelData)
|
||||
|
||||
Connections {
|
||||
target: ScalingService
|
||||
function onScaleChanged(screenName, scale) {
|
||||
@@ -25,318 +25,368 @@ Variants {
|
||||
}
|
||||
}
|
||||
|
||||
active: Settings.isLoaded && modelData ? Settings.data.dock.monitors.includes(modelData.name) : false
|
||||
// Shared properties between peek and dock windows
|
||||
readonly property bool autoHide: Settings.data.dock.autoHide
|
||||
readonly property int hideDelay: 500
|
||||
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
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
id: dockWindow
|
||||
// Bar detection and positioning properties
|
||||
readonly property bool hasBar: 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
|
||||
|
||||
screen: modelData
|
||||
// Shared state between windows
|
||||
property bool dockHovered: false
|
||||
property bool anyAppHovered: false
|
||||
property bool hidden: autoHide
|
||||
property bool peekHovered: false
|
||||
|
||||
WlrLayershell.namespace: "noctalia-dock"
|
||||
// Separate property to control Loader - stays true during animations
|
||||
property bool dockLoaded: !autoHide // Start loaded if autoHide is off
|
||||
|
||||
readonly property bool autoHide: Settings.data.dock.autoHide
|
||||
readonly property int hideDelay: 500
|
||||
readonly property int showDelay: 100
|
||||
readonly property int hideAnimationDuration: Style.animationFast
|
||||
readonly property int showAnimationDuration: Style.animationFast
|
||||
readonly property int peekHeight: 7 * scaling
|
||||
readonly property int fullHeight: dockContainer.height
|
||||
readonly property int iconSize: 36 * scaling
|
||||
readonly property int floatingMargin: 12 * scaling // Margin to make dock float
|
||||
// Timer to unload dock after hide animation completes
|
||||
Timer {
|
||||
id: unloadTimer
|
||||
interval: hideAnimationDuration + 50 // Add small buffer
|
||||
onTriggered: {
|
||||
if (hidden && autoHide) {
|
||||
dockLoaded = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bar detection and positioning properties
|
||||
readonly property bool hasBar: 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 bool barAtTop: hasBar && Settings.data.bar.position === "top"
|
||||
readonly property int barHeight: (barAtBottom || barAtTop) ? (Settings.data.bar.height || 30) * scaling : 0
|
||||
readonly property int dockSpacing: 8 * scaling // Space between dock and bar/edge
|
||||
|
||||
// Track hover state
|
||||
property bool dockHovered: false
|
||||
property bool anyAppHovered: false
|
||||
property bool hidden: autoHide
|
||||
|
||||
// Dock is positioned at the bottom
|
||||
anchors.bottom: true
|
||||
|
||||
// Dock should be above bar but not create its own exclusion zone
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
focusable: false
|
||||
|
||||
// Make the window transparent
|
||||
color: Color.transparent
|
||||
|
||||
// Set the window size - include extra height only if bar is at bottom
|
||||
implicitWidth: dockContainer.width + (floatingMargin * 2)
|
||||
implicitHeight: fullHeight + floatingMargin + (barAtBottom ? barHeight + dockSpacing : 0)
|
||||
|
||||
// Position the entire window above the bar only when bar is at bottom
|
||||
margins.bottom: barAtBottom ? barHeight : 0
|
||||
|
||||
// Watch for autoHide setting changes
|
||||
onAutoHideChanged: {
|
||||
if (!autoHide) {
|
||||
// If auto-hide is disabled, show the dock
|
||||
hidden = false
|
||||
hideTimer.stop()
|
||||
showTimer.stop()
|
||||
} else {
|
||||
// If auto-hide is enabled, start hidden
|
||||
// Timer for auto-hide delay
|
||||
Timer {
|
||||
id: hideTimer
|
||||
interval: hideDelay
|
||||
onTriggered: {
|
||||
if (autoHide && !dockHovered && !anyAppHovered && !peekHovered) {
|
||||
hidden = true
|
||||
unloadTimer.restart() // Start unload timer when hiding
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Timer for auto-hide delay
|
||||
Timer {
|
||||
id: hideTimer
|
||||
interval: hideDelay
|
||||
onTriggered: {
|
||||
if (autoHide && !dockHovered && !anyAppHovered && !peekArea.containsMouse) {
|
||||
hidden = true
|
||||
}
|
||||
// Timer for show delay
|
||||
Timer {
|
||||
id: showTimer
|
||||
interval: showDelay
|
||||
onTriggered: {
|
||||
if (autoHide) {
|
||||
dockLoaded = true // Load dock immediately
|
||||
hidden = false // Then trigger show animation
|
||||
unloadTimer.stop() // Cancel any pending unload
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Timer for show delay
|
||||
Timer {
|
||||
id: showTimer
|
||||
interval: showDelay
|
||||
onTriggered: {
|
||||
if (autoHide) {
|
||||
hidden = false
|
||||
}
|
||||
}
|
||||
// Watch for autoHide setting changes
|
||||
onAutoHideChanged: {
|
||||
if (!autoHide) {
|
||||
hidden = false
|
||||
dockLoaded = true
|
||||
hideTimer.stop()
|
||||
showTimer.stop()
|
||||
unloadTimer.stop()
|
||||
} else {
|
||||
hidden = true
|
||||
unloadTimer.restart() // Schedule unload after animation
|
||||
}
|
||||
}
|
||||
|
||||
// Peek area that remains visible when dock is hidden
|
||||
MouseArea {
|
||||
id: peekArea
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: peekHeight + floatingMargin + (barAtBottom ? dockSpacing : 0)
|
||||
hoverEnabled: autoHide
|
||||
visible: autoHide
|
||||
// PEEK WINDOW - Always visible when auto-hide is enabled
|
||||
Loader {
|
||||
active: Settings.isLoaded && modelData && Settings.data.dock.monitors.includes(modelData.name) && autoHide
|
||||
|
||||
onEntered: {
|
||||
if (autoHide && hidden) {
|
||||
showTimer.start()
|
||||
}
|
||||
}
|
||||
sourceComponent: PanelWindow {
|
||||
id: peekWindow
|
||||
|
||||
onExited: {
|
||||
if (autoHide && !hidden && !dockHovered && !anyAppHovered) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
screen: modelData
|
||||
anchors.bottom: true
|
||||
anchors.left: true
|
||||
anchors.right: true
|
||||
focusable: false
|
||||
color: Color.transparent
|
||||
|
||||
Rectangle {
|
||||
id: dockContainer
|
||||
width: dockLayout.implicitWidth + Style.marginL * scaling * 2
|
||||
height: Math.round(iconSize * 1.6)
|
||||
color: Qt.alpha(Color.mSurface, Settings.data.dock.backgroundOpacity)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: floatingMargin + (barAtBottom ? dockSpacing : 0)
|
||||
radius: Style.radiusL * scaling
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
border.color: Color.mOutline
|
||||
WlrLayershell.namespace: "noctalia-dock-peek"
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Auto // Always exclusive
|
||||
|
||||
// Fade and zoom animation properties
|
||||
opacity: hidden ? 0 : 1
|
||||
scale: hidden ? 0.85 : 1
|
||||
implicitHeight: peekHeight
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: hidden ? hideAnimationDuration : showAnimationDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: hidden ? hideAnimationDuration : showAnimationDuration
|
||||
easing.type: hidden ? Easing.InQuad : Easing.OutBack
|
||||
easing.overshoot: hidden ? 0 : 1.05
|
||||
}
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: barAtBottom ? Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity) : Color.transparent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dockMouseArea
|
||||
id: peekArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
onEntered: {
|
||||
dockHovered = true
|
||||
if (autoHide) {
|
||||
showTimer.stop()
|
||||
hideTimer.stop()
|
||||
if (hidden) {
|
||||
hidden = false
|
||||
}
|
||||
peekHovered = true
|
||||
if (hidden) {
|
||||
showTimer.start()
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
dockHovered = false
|
||||
// Only start hide timer if we're not hovering over any app or the peek area
|
||||
if (autoHide && !anyAppHovered && !peekArea.containsMouse) {
|
||||
peekHovered = false
|
||||
if (!hidden && !dockHovered && !anyAppHovered) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DOCK WINDOW
|
||||
Loader {
|
||||
active: Settings.isLoaded && modelData && Settings.data.dock.monitors.includes(modelData.name) && dockLoaded && ToplevelManager && (ToplevelManager.toplevels.values.length > 0)
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
id: dockWindow
|
||||
|
||||
screen: modelData
|
||||
|
||||
focusable: false
|
||||
color: Color.transparent
|
||||
|
||||
WlrLayershell.namespace: "noctalia-dock-main"
|
||||
WlrLayershell.exclusionMode: Settings.data.dock.exclusive ? ExclusionMode.Auto : ExclusionMode.Ignore
|
||||
|
||||
// Size to fit the dock container exactly
|
||||
implicitWidth: dockContainerWrapper.width
|
||||
implicitHeight: dockContainerWrapper.height
|
||||
|
||||
// Position above the bar if it's at bottom
|
||||
anchors.bottom: true
|
||||
|
||||
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)
|
||||
default:
|
||||
return floatingMargin
|
||||
}
|
||||
}
|
||||
|
||||
// Rectangle {
|
||||
// anchors.fill: parent
|
||||
// color: "#000FF0"
|
||||
// z: -1
|
||||
// }
|
||||
|
||||
// Wrapper item for scale/opacity animations
|
||||
Item {
|
||||
id: dock
|
||||
width: dockLayout.implicitWidth
|
||||
height: parent.height - (Style.marginM * 2 * scaling)
|
||||
anchors.centerIn: parent
|
||||
id: dockContainerWrapper
|
||||
width: dockContainer.width
|
||||
height: dockContainer.height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
function getAppIcon(toplevel: Toplevel): string {
|
||||
if (!toplevel)
|
||||
return ""
|
||||
return AppIcons.iconForAppId(toplevel.appId?.toLowerCase())
|
||||
// Apply animations to this wrapper
|
||||
opacity: hidden ? 0 : 1
|
||||
scale: hidden ? 0.85 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: hidden ? hideAnimationDuration : showAnimationDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: dockLayout
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.preferredHeight: parent.height
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: hidden ? hideAnimationDuration : showAnimationDuration
|
||||
easing.type: hidden ? Easing.InQuad : Easing.OutBack
|
||||
easing.overshoot: hidden ? 0 : 1.05
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: dockContainer
|
||||
width: dockLayout.implicitWidth + Style.marginM * scaling * 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)
|
||||
border.color: Qt.alpha(Color.mOutline, Settings.data.dock.backgroundOpacity)
|
||||
|
||||
Repeater {
|
||||
model: ToplevelManager ? ToplevelManager.toplevels : null
|
||||
MouseArea {
|
||||
id: dockMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
delegate: Item {
|
||||
id: appButton
|
||||
Layout.preferredWidth: iconSize
|
||||
Layout.preferredHeight: iconSize
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
property bool isActive: ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === modelData
|
||||
property bool hovered: appMouseArea.containsMouse
|
||||
property string appId: modelData ? modelData.appId : ""
|
||||
property string appTitle: modelData ? modelData.title : ""
|
||||
|
||||
// Individual tooltip for this app
|
||||
NTooltip {
|
||||
id: appTooltip
|
||||
target: appButton
|
||||
positionAbove: true
|
||||
visible: false
|
||||
onEntered: {
|
||||
dockHovered = true
|
||||
if (autoHide) {
|
||||
showTimer.stop()
|
||||
hideTimer.stop()
|
||||
unloadTimer.stop() // Cancel unload if hovering
|
||||
}
|
||||
}
|
||||
|
||||
// The icon with better quality settings
|
||||
Image {
|
||||
id: appIcon
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
anchors.centerIn: parent
|
||||
source: dock.getAppIcon(modelData)
|
||||
visible: source.toString() !== ""
|
||||
sourceSize.width: iconSize * 2
|
||||
sourceSize.height: iconSize * 2
|
||||
smooth: true
|
||||
mipmap: true
|
||||
antialiasing: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
cache: true
|
||||
onExited: {
|
||||
dockHovered = false
|
||||
if (autoHide && !anyAppHovered && !peekHovered) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scale: appButton.hovered ? 1.15 : 1.0
|
||||
Item {
|
||||
id: dock
|
||||
width: dockLayout.implicitWidth
|
||||
height: parent.height - (Style.marginM * 2 * scaling)
|
||||
anchors.centerIn: parent
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutBack
|
||||
easing.overshoot: 1.2
|
||||
function getAppIcon(toplevel: Toplevel): string {
|
||||
if (!toplevel)
|
||||
return ""
|
||||
return AppIcons.iconForAppId(toplevel.appId?.toLowerCase())
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: dockLayout
|
||||
spacing: Style.marginM * scaling
|
||||
Layout.preferredHeight: parent.height
|
||||
anchors.centerIn: parent
|
||||
|
||||
Repeater {
|
||||
model: ToplevelManager ? ToplevelManager.toplevels : null
|
||||
|
||||
delegate: Item {
|
||||
id: appButton
|
||||
Layout.preferredWidth: iconSize
|
||||
Layout.preferredHeight: iconSize
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
property bool isActive: ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === modelData
|
||||
property bool hovered: appMouseArea.containsMouse
|
||||
property string appId: modelData ? modelData.appId : ""
|
||||
property string appTitle: modelData ? modelData.title : ""
|
||||
|
||||
// Individual tooltip for this app
|
||||
NTooltip {
|
||||
id: appTooltip
|
||||
target: appButton
|
||||
positionAbove: true
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back if no icon
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: !appIcon.visible
|
||||
icon: "question-mark"
|
||||
font.pointSize: iconSize * 0.7
|
||||
color: appButton.isActive ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
scale: appButton.hovered ? 1.15 : 1.0
|
||||
Image {
|
||||
id: appIcon
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
anchors.centerIn: parent
|
||||
source: dock.getAppIcon(modelData)
|
||||
visible: source.toString() !== ""
|
||||
sourceSize.width: iconSize * 2
|
||||
sourceSize.height: iconSize * 2
|
||||
smooth: true
|
||||
mipmap: true
|
||||
antialiasing: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
cache: true
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutBack
|
||||
easing.overshoot: 1.2
|
||||
}
|
||||
}
|
||||
}
|
||||
scale: appButton.hovered ? 1.15 : 1.0
|
||||
|
||||
MouseArea {
|
||||
id: appMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
|
||||
|
||||
onEntered: {
|
||||
anyAppHovered = true
|
||||
const appName = appButton.appTitle || appButton.appId || "Unknown"
|
||||
appTooltip.text = appName.length > 40 ? appName.substring(0, 37) + "..." : appName
|
||||
appTooltip.isVisible = true
|
||||
if (autoHide) {
|
||||
showTimer.stop()
|
||||
hideTimer.stop()
|
||||
if (hidden) {
|
||||
hidden = false
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutBack
|
||||
easing.overshoot: 1.2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
anyAppHovered = false
|
||||
appTooltip.hide()
|
||||
// Only start hide timer if we're not hovering over the dock or peek area
|
||||
if (autoHide && !dockHovered && !peekArea.containsMouse) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
// Fall back if no icon
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: !appIcon.visible
|
||||
icon: "question-mark"
|
||||
font.pointSize: iconSize * 0.7
|
||||
color: appButton.isActive ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
scale: appButton.hovered ? 1.15 : 1.0
|
||||
|
||||
onClicked: function (mouse) {
|
||||
if (mouse.button === Qt.MiddleButton && modelData?.close) {
|
||||
modelData.close()
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutBack
|
||||
easing.overshoot: 1.2
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mouse.button === Qt.LeftButton && modelData?.activate) {
|
||||
modelData.activate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Active indicator
|
||||
Rectangle {
|
||||
visible: isActive
|
||||
width: iconSize * 0.2
|
||||
height: iconSize * 0.1
|
||||
color: Color.mPrimary
|
||||
radius: Style.radiusXS * scaling
|
||||
anchors.top: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: Style.marginXXS * 1.5 * scaling
|
||||
MouseArea {
|
||||
id: appMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
|
||||
|
||||
// Pulse animation for active indicator
|
||||
SequentialAnimation on opacity {
|
||||
running: isActive
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation {
|
||||
to: 0.6
|
||||
duration: Style.animationSlowest
|
||||
easing.type: Easing.InOutQuad
|
||||
onEntered: {
|
||||
anyAppHovered = true
|
||||
const appName = appButton.appTitle || appButton.appId || "Unknown"
|
||||
appTooltip.text = appName.length > 40 ? appName.substring(0, 37) + "..." : appName
|
||||
appTooltip.isVisible = true
|
||||
if (autoHide) {
|
||||
showTimer.stop()
|
||||
hideTimer.stop()
|
||||
unloadTimer.stop() // Cancel unload if hovering app
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
anyAppHovered = false
|
||||
appTooltip.hide()
|
||||
if (autoHide && !dockHovered && !peekHovered) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: function (mouse) {
|
||||
if (mouse.button === Qt.MiddleButton && modelData?.close) {
|
||||
modelData.close()
|
||||
}
|
||||
if (mouse.button === Qt.LeftButton && modelData?.activate) {
|
||||
modelData.activate()
|
||||
}
|
||||
}
|
||||
}
|
||||
NumberAnimation {
|
||||
to: 1.0
|
||||
duration: Style.animationSlowest
|
||||
easing.type: Easing.InOutQuad
|
||||
|
||||
// Active indicator
|
||||
Rectangle {
|
||||
visible: isActive
|
||||
width: iconSize * 0.2
|
||||
height: iconSize * 0.1
|
||||
color: Color.mPrimary
|
||||
radius: Style.radiusXS * scaling
|
||||
anchors.top: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
// Pulse animation for active indicator
|
||||
SequentialAnimation on opacity {
|
||||
running: isActive
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation {
|
||||
to: 0.6
|
||||
duration: Style.animationSlowest
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
NumberAnimation {
|
||||
to: 1.0
|
||||
duration: Style.animationSlowest
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,17 +8,6 @@ import qs.Services
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Using Wayland protocols to get focused window then determine which screen it's on.
|
||||
function getActiveScreen() {
|
||||
const activeWindow = ToplevelManager.activeToplevel
|
||||
if (activeWindow && activeWindow.screens.length > 0) {
|
||||
return activeWindow.screens[0]
|
||||
}
|
||||
|
||||
// Fall back to the primary screen
|
||||
return Quickshell.screens[0]
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "screenRecorder"
|
||||
function toggle() {
|
||||
@@ -31,14 +20,14 @@ Item {
|
||||
IpcHandler {
|
||||
target: "settings"
|
||||
function toggle() {
|
||||
settingsPanel.toggle(getActiveScreen())
|
||||
settingsPanel.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "notifications"
|
||||
function toggleHistory() {
|
||||
notificationHistoryPanel.toggle(getActiveScreen())
|
||||
notificationHistoryPanel.toggle()
|
||||
}
|
||||
function toggleDND() {
|
||||
Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
|
||||
@@ -55,15 +44,15 @@ Item {
|
||||
IpcHandler {
|
||||
target: "launcher"
|
||||
function toggle() {
|
||||
launcherPanel.toggle(getActiveScreen())
|
||||
launcherPanel.toggle()
|
||||
}
|
||||
function clipboard() {
|
||||
launcherPanel.setSearchText(">clip ")
|
||||
launcherPanel.toggle(getActiveScreen())
|
||||
launcherPanel.toggle()
|
||||
}
|
||||
function calculator() {
|
||||
launcherPanel.setSearchText(">calc ")
|
||||
launcherPanel.toggle(getActiveScreen())
|
||||
launcherPanel.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +99,7 @@ Item {
|
||||
AudioService.decreaseVolume()
|
||||
}
|
||||
function muteOutput() {
|
||||
AudioService.setMuted(!AudioService.muted)
|
||||
AudioService.setOutputMuted(!AudioService.muted)
|
||||
}
|
||||
function muteInput() {
|
||||
if (AudioService.source?.ready && AudioService.source?.audio) {
|
||||
@@ -122,14 +111,14 @@ Item {
|
||||
IpcHandler {
|
||||
target: "powerPanel"
|
||||
function toggle() {
|
||||
powerPanel.toggle(getActiveScreen())
|
||||
powerPanel.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "sidePanel"
|
||||
function toggle() {
|
||||
sidePanel.toggle(getActiveScreen())
|
||||
sidePanel.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,16 +11,10 @@ NPanel {
|
||||
id: root
|
||||
|
||||
// Panel configuration
|
||||
panelWidth: {
|
||||
var w = Math.round(Math.max(screen?.width * 0.3, 500) * scaling)
|
||||
w = Math.min(w, screen?.width - Style.marginL * 2)
|
||||
return w
|
||||
}
|
||||
panelHeight: {
|
||||
var h = Math.round(Math.max(screen?.height * 0.5, 600) * scaling)
|
||||
h = Math.min(h, screen?.height - Style.barHeight * scaling - Style.marginL * 2)
|
||||
return h
|
||||
}
|
||||
preferredWidth: 500
|
||||
preferredWidthRatio: 0.3
|
||||
preferredHeight: 600
|
||||
preferredHeightRatio: 0.5
|
||||
|
||||
panelKeyboardFocus: true
|
||||
panelBackgroundColor: Qt.alpha(Color.mSurface, Settings.data.appLauncher.backgroundOpacity)
|
||||
@@ -287,9 +281,12 @@ NPanel {
|
||||
}
|
||||
|
||||
// Results list
|
||||
ListView {
|
||||
NListView {
|
||||
id: resultsList
|
||||
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: Style.marginXXS * scaling
|
||||
@@ -306,10 +303,6 @@ NPanel {
|
||||
}
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
id: entry
|
||||
|
||||
|
||||
@@ -37,8 +37,7 @@ Item {
|
||||
|
||||
if (!query || query.trim() === "") {
|
||||
// Return all apps alphabetically
|
||||
return entries.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())).map(
|
||||
app => createResultEntry(app))
|
||||
return entries.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())).map(app => createResultEntry(app))
|
||||
}
|
||||
|
||||
// Use fuzzy search if available, fallback to simple search
|
||||
@@ -57,8 +56,7 @@ Item {
|
||||
const name = (app.name || "").toLowerCase()
|
||||
const comment = (app.comment || "").toLowerCase()
|
||||
const generic = (app.genericName || "").toLowerCase()
|
||||
return name.includes(searchTerm) || comment.includes(searchTerm) || generic.includes(
|
||||
searchTerm)
|
||||
return name.includes(searchTerm) || comment.includes(searchTerm) || generic.includes(searchTerm)
|
||||
}).sort((a, b) => {
|
||||
// Prioritize name matches
|
||||
const aName = a.name.toLowerCase()
|
||||
@@ -85,7 +83,10 @@ Item {
|
||||
|
||||
if (Settings.data.appLauncher.useApp2Unit && app.id) {
|
||||
Logger.log("ApplicationsPlugin", `Using app2unit for: ${app.id}`)
|
||||
Quickshell.execDetached(["app2unit", "--", app.id + ".desktop"])
|
||||
if (app.runInTerminal)
|
||||
Quickshell.execDetached(["app2unit", "--", app.id + ".desktop"])
|
||||
else
|
||||
Quickshell.execDetached(["app2unit", "--"].concat(app.command))
|
||||
} else if (app.execute) {
|
||||
app.execute()
|
||||
} else if (app.exec) {
|
||||
|
||||
@@ -8,8 +8,7 @@ Item {
|
||||
|
||||
function handleCommand(query) {
|
||||
// Handle >calc command or direct math expressions after >
|
||||
return query.startsWith(">calc") || (query.startsWith(">") && query.length > 1 && isMathExpression(
|
||||
query.substring(1)))
|
||||
return query.startsWith(">calc") || (query.startsWith(">") && query.length > 1 && isMathExpression(query.substring(1)))
|
||||
}
|
||||
|
||||
function commands() {
|
||||
|
||||
@@ -12,6 +12,7 @@ Scope {
|
||||
property bool unlockInProgress: false
|
||||
property bool showFailure: false
|
||||
property string errorMessage: ""
|
||||
property string infoMessage: ""
|
||||
property bool pamAvailable: typeof PamContext !== "undefined"
|
||||
|
||||
onCurrentTextChanged: {
|
||||
@@ -28,12 +29,6 @@ Scope {
|
||||
return
|
||||
}
|
||||
|
||||
if (currentText === "") {
|
||||
errorMessage = "Password required"
|
||||
showFailure = true
|
||||
return
|
||||
}
|
||||
|
||||
root.unlockInProgress = true
|
||||
errorMessage = ""
|
||||
showFailure = false
|
||||
@@ -48,11 +43,12 @@ Scope {
|
||||
user: Quickshell.env("USER")
|
||||
|
||||
onPamMessage: {
|
||||
Logger.log("LockContext", "PAM message:", message, "isError:", messageIsError, "responseRequired:",
|
||||
responseRequired)
|
||||
Logger.log("LockContext", "PAM message:", message, "isError:", messageIsError, "responseRequired:", responseRequired)
|
||||
|
||||
if (messageIsError) {
|
||||
errorMessage = message
|
||||
} else {
|
||||
infoMessage = message
|
||||
}
|
||||
|
||||
if (responseRequired) {
|
||||
|
||||
@@ -62,8 +62,7 @@ Loader {
|
||||
|
||||
Item {
|
||||
id: keyboardLayout
|
||||
property string currentLayout: (typeof KeyboardLayoutService !== 'undefined'
|
||||
&& KeyboardLayoutService.currentLayout) ? KeyboardLayoutService.currentLayout : "Unknown"
|
||||
property string currentLayout: (typeof KeyboardLayoutService !== 'undefined' && KeyboardLayoutService.currentLayout) ? KeyboardLayoutService.currentLayout : "Unknown"
|
||||
}
|
||||
|
||||
Image {
|
||||
@@ -227,12 +226,10 @@ Loader {
|
||||
Repeater {
|
||||
model: CavaService.values.length * 2
|
||||
Rectangle {
|
||||
property int mirroredValueIndex: index < CavaService.values.length ? index : (CavaService.values.length
|
||||
* 2 - 1 - index)
|
||||
property int mirroredValueIndex: index < CavaService.values.length ? index : (CavaService.values.length * 2 - 1 - index)
|
||||
property real mirroredAngle: (index / (CavaService.values.length * 2)) * 2 * Math.PI
|
||||
property real mirroredRadius: 70 * scaling
|
||||
property real mirroredBarLength: Math.max(
|
||||
2, CavaService.values[mirroredValueIndex] * 30 * scaling)
|
||||
property real mirroredBarLength: Math.max(2, CavaService.values[mirroredValueIndex] * 30 * scaling)
|
||||
property real mirroredBarWidth: 3 * scaling
|
||||
width: mirroredBarWidth
|
||||
height: mirroredBarLength
|
||||
@@ -428,8 +425,7 @@ Loader {
|
||||
spacing: Style.marginS * scaling
|
||||
visible: batteryIndicator.batteryVisible
|
||||
NIcon {
|
||||
icon: BatteryService.getIcon(batteryIndicator.percent, batteryIndicator.charging,
|
||||
batteryIndicator.isReady)
|
||||
icon: BatteryService.getIcon(batteryIndicator.percent, batteryIndicator.charging, batteryIndicator.isReady)
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurface
|
||||
rotation: -90
|
||||
@@ -515,6 +511,7 @@ Loader {
|
||||
width: 0
|
||||
height: 0
|
||||
visible: false
|
||||
enabled: !lockContext.unlockInProgress
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: Color.mOnSurface
|
||||
@@ -544,7 +541,7 @@ Loader {
|
||||
color: Color.mOnSurface
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
visible: passwordInput.activeFocus
|
||||
visible: passwordInput.activeFocus && !lockContext.unlockInProgress
|
||||
|
||||
SequentialAnimation {
|
||||
id: typingEffect
|
||||
@@ -588,7 +585,7 @@ Loader {
|
||||
NText {
|
||||
text: {
|
||||
if (lockContext.unlockInProgress)
|
||||
return "Authenticating..."
|
||||
return lockContext.infoMessage || "Authenticating..."
|
||||
if (lockContext.showFailure && lockContext.errorMessage)
|
||||
return lockContext.errorMessage
|
||||
if (lockContext.showFailure)
|
||||
@@ -750,7 +747,7 @@ Loader {
|
||||
id: shutdownTooltipText
|
||||
anchors.margins: Style.marginM * scaling
|
||||
anchors.fill: parent
|
||||
text: "Shut down the computer."
|
||||
text: "Shut down."
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -801,7 +798,7 @@ Loader {
|
||||
id: restartTooltipText
|
||||
anchors.margins: Style.marginM * scaling
|
||||
anchors.fill: parent
|
||||
text: "Restart the computer."
|
||||
text: "Restart."
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -853,7 +850,7 @@ Loader {
|
||||
id: suspendTooltipText
|
||||
anchors.margins: Style.marginM * scaling
|
||||
anchors.fill: parent
|
||||
text: "Suspend the system."
|
||||
text: "Suspend."
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
@@ -25,10 +25,7 @@ Variants {
|
||||
property var removingNotifications: ({})
|
||||
|
||||
// If no notification display activated in settings, then show them all
|
||||
active: Settings.isLoaded && modelData
|
||||
&& (NotificationService.notificationModel.count > 0) ? (Settings.data.notifications.monitors.includes(
|
||||
modelData.name)
|
||||
|| (Settings.data.notifications.monitors.length === 0)) : false
|
||||
active: Settings.isLoaded && modelData && (NotificationService.notificationModel.count > 0) ? (Settings.data.notifications.monitors.includes(modelData.name) || (Settings.data.notifications.monitors.length === 0)) : false
|
||||
|
||||
visible: (NotificationService.notificationModel.count > 0)
|
||||
|
||||
@@ -36,13 +33,50 @@ Variants {
|
||||
screen: modelData
|
||||
color: Color.transparent
|
||||
|
||||
// Position based on bar location
|
||||
anchors.top: Settings.data.bar.position === "top"
|
||||
anchors.bottom: Settings.data.bar.position === "bottom"
|
||||
anchors.right: true
|
||||
margins.top: Settings.data.bar.position === "top" ? (Style.barHeight + Style.marginM) * scaling : 0
|
||||
margins.bottom: Settings.data.bar.position === "bottom" ? (Style.barHeight + Style.marginM) * scaling : 0
|
||||
margins.right: Style.marginM * scaling
|
||||
// Position based on bar location - always at top
|
||||
anchors.top: true
|
||||
anchors.right: Settings.data.bar.position === "right" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom"
|
||||
anchors.left: Settings.data.bar.position === "left"
|
||||
|
||||
margins.top: {
|
||||
switch (Settings.data.bar.position) {
|
||||
case "top":
|
||||
return (Style.barHeight + Style.marginM) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0)
|
||||
default:
|
||||
return Style.marginM * scaling
|
||||
}
|
||||
}
|
||||
|
||||
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 : 0)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
margins.left: {
|
||||
switch (Settings.data.bar.position) {
|
||||
case "left":
|
||||
return (Style.barHeight + Style.marginM) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
margins.right: {
|
||||
switch (Settings.data.bar.position) {
|
||||
case "right":
|
||||
return (Style.barHeight + Style.marginM) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0)
|
||||
case "top":
|
||||
case "bottom":
|
||||
return Style.marginM * scaling
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
implicitWidth: 360 * scaling
|
||||
implicitHeight: Math.min(notificationStack.implicitHeight, (NotificationService.maxVisible * 120) * scaling)
|
||||
//WlrLayershell.layer: WlrLayer.Overlay
|
||||
@@ -80,10 +114,10 @@ Variants {
|
||||
// Main notification container
|
||||
ColumnLayout {
|
||||
id: notificationStack
|
||||
// Position based on bar location
|
||||
anchors.top: Settings.data.bar.position === "top" ? parent.top : undefined
|
||||
anchors.bottom: Settings.data.bar.position === "bottom" ? parent.bottom : undefined
|
||||
anchors.right: parent.right
|
||||
// Position based on bar location - always at top
|
||||
anchors.top: parent.top
|
||||
anchors.right: (Settings.data.bar.position === "right" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom") ? parent.right : undefined
|
||||
anchors.left: Settings.data.bar.position === "left" ? parent.left : undefined
|
||||
spacing: Style.marginS * scaling
|
||||
width: 360 * scaling
|
||||
visible: true
|
||||
@@ -181,8 +215,7 @@ Variants {
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
NText {
|
||||
text: `${(model.appName || model.desktopEntry)
|
||||
|| "Unknown App"} · ${NotificationService.formatTimestamp(model.timestamp)}`
|
||||
text: `${(model.appName || model.desktopEntry) || "Unknown App"} · ${NotificationService.formatTimestamp(model.timestamp)}`
|
||||
color: Color.mSecondary
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
}
|
||||
@@ -249,8 +282,7 @@ Variants {
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS * scaling
|
||||
visible: model.rawNotification && model.rawNotification.actions
|
||||
&& model.rawNotification.actions.length > 0
|
||||
visible: model.rawNotification && model.rawNotification.actions && model.rawNotification.actions.length > 0
|
||||
|
||||
property var notificationActions: model.rawNotification ? model.rawNotification.actions : []
|
||||
|
||||
@@ -293,7 +325,7 @@ Variants {
|
||||
// Close button positioned absolutely
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: "Close"
|
||||
tooltipText: "Close."
|
||||
sizeRatio: 0.6
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Style.marginM * scaling
|
||||
|
||||
@@ -12,9 +12,10 @@ import qs.Widgets
|
||||
NPanel {
|
||||
id: root
|
||||
|
||||
panelWidth: 380 * scaling
|
||||
panelHeight: 500 * scaling
|
||||
panelAnchorRight: true
|
||||
preferredWidth: 380
|
||||
preferredHeight: 500
|
||||
panelAnchorRight: Settings.data.bar.position === "right"
|
||||
panelKeyboardFocus: true
|
||||
|
||||
panelContent: Rectangle {
|
||||
id: notificationRect
|
||||
@@ -56,12 +57,15 @@ NPanel {
|
||||
icon: "trash"
|
||||
tooltipText: "Clear history"
|
||||
sizeRatio: 0.8
|
||||
onClicked: NotificationService.clearHistory()
|
||||
onClicked: {
|
||||
NotificationService.clearHistory()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: "Close"
|
||||
tooltipText: "Close."
|
||||
sizeRatio: 0.8
|
||||
onClicked: {
|
||||
root.close()
|
||||
@@ -115,10 +119,13 @@ NPanel {
|
||||
}
|
||||
|
||||
// Notification list
|
||||
ListView {
|
||||
NListView {
|
||||
id: notificationList
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
|
||||
model: NotificationService.historyModel
|
||||
spacing: Style.marginM * scaling
|
||||
clip: true
|
||||
@@ -129,7 +136,7 @@ NPanel {
|
||||
width: notificationList.width
|
||||
height: notificationLayout.implicitHeight + (Style.marginM * scaling * 2)
|
||||
radius: Style.radiusM * scaling
|
||||
color: notificationMouseArea.containsMouse ? Color.mSecondary : Color.mSurfaceVariant
|
||||
color: notificationMouseArea.containsMouse ? Color.mTertiary : Color.mSurfaceVariant
|
||||
border.color: Qt.alpha(Color.mOutline, Style.opacityMedium)
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
|
||||
@@ -145,13 +152,7 @@ NPanel {
|
||||
Layout.preferredHeight: 28 * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
// Prefer stable themed icons over transient image paths
|
||||
imagePath: (appIcon
|
||||
&& appIcon !== "") ? (AppIcons.iconFromName(appIcon, "application-x-executable")
|
||||
|| appIcon) : ((AppIcons.iconForAppId(desktopEntry
|
||||
|| appName, "application-x-executable")
|
||||
|| (image && image
|
||||
!== "" ? image : AppIcons.iconFromName("application-x-executable",
|
||||
"application-x-executable"))))
|
||||
imagePath: (appIcon && appIcon !== "") ? (AppIcons.iconFromName(appIcon, "application-x-executable") || appIcon) : ((AppIcons.iconForAppId(desktopEntry || appName, "application-x-executable") || (image && image !== "" ? image : AppIcons.iconFromName("application-x-executable", "application-x-executable"))))
|
||||
borderColor: Color.transparent
|
||||
borderWidth: 0
|
||||
visible: true
|
||||
@@ -168,7 +169,7 @@ NPanel {
|
||||
text: (summary || "No summary").substring(0, 100)
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Font.Medium
|
||||
color: notificationMouseArea.containsMouse ? Color.mSurface : Color.mPrimary
|
||||
color: notificationMouseArea.containsMouse ? Color.mOnTertiary : Color.mPrimary
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
maximumLineCount: 2
|
||||
@@ -178,7 +179,7 @@ NPanel {
|
||||
NText {
|
||||
text: (body || "").substring(0, 150)
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: notificationMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface
|
||||
color: notificationMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
maximumLineCount: 3
|
||||
@@ -189,7 +190,7 @@ NPanel {
|
||||
NText {
|
||||
text: NotificationService.formatTimestamp(timestamp)
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: notificationMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface
|
||||
color: notificationMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ import qs.Widgets
|
||||
NPanel {
|
||||
id: root
|
||||
|
||||
panelWidth: 440 * scaling
|
||||
panelHeight: 380 * scaling
|
||||
preferredWidth: 440
|
||||
preferredHeight: 410
|
||||
panelAnchorHorizontalCenter: true
|
||||
panelAnchorVerticalCenter: true
|
||||
panelKeyboardFocus: true
|
||||
@@ -224,6 +224,7 @@ NPanel {
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
context: Qt.WidgetShortcut
|
||||
enabled: root.opened
|
||||
}
|
||||
|
||||
@@ -262,8 +263,7 @@ NPanel {
|
||||
Layout.preferredHeight: Style.baseWidgetSize * 0.8 * scaling
|
||||
|
||||
NText {
|
||||
text: timerActive ? `${pendingAction.charAt(0).toUpperCase() + pendingAction.slice(1)} in ${Math.ceil(
|
||||
timeRemaining / 1000)} seconds...` : "Power Options"
|
||||
text: timerActive ? `${pendingAction.charAt(0).toUpperCase() + pendingAction.slice(1)} in ${Math.ceil(timeRemaining / 1000)} seconds...` : "Power Menu"
|
||||
font.weight: Style.fontWeightBold
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: timerActive ? Color.mPrimary : Color.mOnSurface
|
||||
@@ -292,6 +292,10 @@ NPanel {
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Power options
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
@@ -337,7 +341,7 @@ NPanel {
|
||||
return Qt.alpha(Color.mPrimary, 0.08)
|
||||
}
|
||||
if (isSelected || mouseArea.containsMouse) {
|
||||
return Color.mSecondary
|
||||
return Color.mTertiary
|
||||
}
|
||||
return Color.transparent
|
||||
}
|
||||
@@ -367,7 +371,7 @@ NPanel {
|
||||
if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse)
|
||||
return Color.mError
|
||||
if (buttonRoot.isSelected || mouseArea.containsMouse)
|
||||
return Color.mOnSecondary
|
||||
return Color.mOnTertiary
|
||||
return Color.mOnSurface
|
||||
}
|
||||
font.pointSize: Style.fontSizeXXXL * scaling
|
||||
@@ -401,7 +405,7 @@ NPanel {
|
||||
if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse)
|
||||
return Color.mError
|
||||
if (buttonRoot.isSelected || mouseArea.containsMouse)
|
||||
return Color.mOnSecondary
|
||||
return Color.mOnTertiary
|
||||
return Color.mOnSurface
|
||||
}
|
||||
|
||||
@@ -426,7 +430,7 @@ NPanel {
|
||||
if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse)
|
||||
return Color.mError
|
||||
if (buttonRoot.isSelected || mouseArea.containsMouse)
|
||||
return Color.mOnSecondary
|
||||
return Color.mOnTertiary
|
||||
return Color.mOnSurfaceVariant
|
||||
}
|
||||
opacity: Style.opacityHeavy
|
||||
|
||||
@@ -19,10 +19,8 @@ NBox {
|
||||
signal reorderWidget(string section, int fromIndex, int toIndex)
|
||||
signal updateWidgetSettings(string section, int index, var settings)
|
||||
signal dragPotentialStarted
|
||||
// Emitted when a widget is pressed (potential drag start)
|
||||
signal dragPotentialEnded
|
||||
|
||||
// Emitted when interaction ends (drag or click)
|
||||
color: Color.mSurface
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: {
|
||||
@@ -43,17 +41,19 @@ NBox {
|
||||
const totalSum = JSON.stringify(widget).split('').reduce((acc, character) => {
|
||||
return acc + character.charCodeAt(0)
|
||||
}, 0)
|
||||
switch (totalSum % 5) {
|
||||
switch (totalSum % 6) {
|
||||
case 0:
|
||||
return Color.mPrimary
|
||||
return [Color.mPrimary, Color.mOnPrimary]
|
||||
case 1:
|
||||
return Color.mSecondary
|
||||
return [Color.mSecondary, Color.mOnSecondary]
|
||||
case 2:
|
||||
return Color.mTertiary
|
||||
return [Color.mTertiary, Color.mOnTertiary]
|
||||
case 3:
|
||||
return Color.mError
|
||||
return [Color.mError, Color.mOnError]
|
||||
case 4:
|
||||
return Color.mOnSurface
|
||||
return [Color.mOnSurface, Color.mSurface]
|
||||
case 5:
|
||||
return [Color.mOnSurfaceVariant, Color.mSurfaceVariant]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ NBox {
|
||||
width: widgetContent.implicitWidth + Style.marginL * scaling
|
||||
height: Style.baseWidgetSize * 1.15 * scaling
|
||||
radius: Style.radiusL * scaling
|
||||
color: root.getWidgetColor(modelData)
|
||||
color: root.getWidgetColor(modelData)[0]
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
|
||||
@@ -147,12 +147,12 @@ NBox {
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ NBox {
|
||||
NText {
|
||||
text: modelData.id
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
color: Color.mOnPrimary
|
||||
color: root.getWidgetColor(modelData)[1]
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
Layout.preferredWidth: 80 * scaling
|
||||
@@ -240,7 +240,7 @@ NBox {
|
||||
width: 0
|
||||
height: Style.baseWidgetSize * 1.15 * scaling
|
||||
radius: Style.radiusL * scaling
|
||||
color: "transparent"
|
||||
color: Color.transparent
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
opacity: 0.7
|
||||
@@ -305,7 +305,7 @@ NBox {
|
||||
|
||||
acceptedButtons: Qt.LeftButton
|
||||
preventStealing: false
|
||||
propagateComposedEvents: !dragStarted
|
||||
propagateComposedEvents: false
|
||||
hoverEnabled: true // Always track mouse for drag operations
|
||||
|
||||
property point startPos: Qt.point(0, 0)
|
||||
@@ -339,12 +339,10 @@ NBox {
|
||||
continue
|
||||
|
||||
// Check distance to left edge (insert before)
|
||||
const leftDist = Math.sqrt(Math.pow(mouseX - widget.x,
|
||||
2) + Math.pow(mouseY - (widget.y + widget.height / 2), 2))
|
||||
const leftDist = Math.sqrt(Math.pow(mouseX - widget.x, 2) + Math.pow(mouseY - (widget.y + widget.height / 2), 2))
|
||||
|
||||
// Check distance to right edge (insert after)
|
||||
const rightDist = Math.sqrt(Math.pow(mouseX - (widget.x + widget.width),
|
||||
2) + Math.pow(mouseY - (widget.y + widget.height / 2), 2))
|
||||
const rightDist = Math.sqrt(Math.pow(mouseX - (widget.x + widget.width), 2) + Math.pow(mouseY - (widget.y + widget.height / 2), 2))
|
||||
|
||||
if (leftDist < minDistance) {
|
||||
minDistance = leftDist
|
||||
@@ -355,8 +353,7 @@ NBox {
|
||||
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 * scaling - dropIndicator.width / 2, widget.y)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,8 +365,7 @@ 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 * scaling), firstWidget.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -419,8 +415,7 @@ NBox {
|
||||
for (var i = 0; i < widgetModel.length; i++) {
|
||||
const widget = widgetFlow.children[i]
|
||||
if (widget && widget.widgetIndex !== undefined) {
|
||||
if (mouse.x >= widget.x && mouse.x <= widget.x + widget.width && mouse.y >= widget.y
|
||||
&& mouse.y <= widget.y + widget.height) {
|
||||
if (mouse.x >= widget.x && mouse.x <= widget.x + widget.width && mouse.y >= widget.y && mouse.y <= widget.y + widget.height) {
|
||||
|
||||
const localX = mouse.x - widget.x
|
||||
const buttonsStartX = widget.width - (widget.buttonsCount * widget.buttonsWidth)
|
||||
@@ -458,7 +453,7 @@ NBox {
|
||||
// Setup ghost widget
|
||||
if (draggedWidget) {
|
||||
dragGhost.width = draggedWidget.width
|
||||
dragGhost.color = root.getWidgetColor(draggedModelData)
|
||||
dragGhost.color = root.getWidgetColor(draggedModelData)[0]
|
||||
ghostText.text = draggedModelData.id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ Popup {
|
||||
x: (parent.width - width) * 0.5
|
||||
y: (parent.height - height) * 0.5
|
||||
|
||||
width: 420 * scaling
|
||||
width: 500 * scaling
|
||||
height: content.implicitHeight + padding * 2
|
||||
padding: Style.marginXL * scaling
|
||||
modal: true
|
||||
@@ -46,6 +46,7 @@ Popup {
|
||||
"Brightness": "WidgetSettings/BrightnessSettings.qml",
|
||||
"Clock": "WidgetSettings/ClockSettings.qml",
|
||||
"CustomButton": "WidgetSettings/CustomButtonSettings.qml",
|
||||
"KeyboardLayout": "WidgetSettings/KeyboardLayoutSettings.qml",
|
||||
"MediaMini": "WidgetSettings/MediaMiniSettings.qml",
|
||||
"Microphone": "WidgetSettings/MicrophoneSettings.qml",
|
||||
"NotificationHistory": "WidgetSettings/NotificationHistorySettings.qml",
|
||||
|
||||
@@ -14,22 +14,36 @@ ColumnLayout {
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local state
|
||||
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
|
||||
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
property int valueWarningThreshold: widgetData.warningThreshold
|
||||
!== undefined ? widgetData.warningThreshold : widgetMetadata.warningThreshold
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
property int valueWarningThreshold: widgetData.warningThreshold !== undefined ? widgetData.warningThreshold : widgetMetadata.warningThreshold
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.alwaysShowPercentage = valueAlwaysShowPercentage
|
||||
settings.displayMode = valueDisplayMode
|
||||
settings.warningThreshold = valueWarningThreshold
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Always show percentage"
|
||||
checked: root.valueAlwaysShowPercentage
|
||||
onToggled: checked => root.valueAlwaysShowPercentage = checked
|
||||
NComboBox {
|
||||
label: "Display mode"
|
||||
description: "Choose how you'd like this value to appear."
|
||||
minimumWidth: 134 * scaling
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "onhover"
|
||||
name: "On Hover"
|
||||
}
|
||||
ListElement {
|
||||
key: "alwaysShow"
|
||||
name: "Always Show"
|
||||
}
|
||||
ListElement {
|
||||
key: "alwaysHide"
|
||||
name: "Always Hide"
|
||||
}
|
||||
}
|
||||
currentKey: root.valueDisplayMode
|
||||
onSelected: key => root.valueDisplayMode = key
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
|
||||
@@ -14,18 +14,33 @@ ColumnLayout {
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local state
|
||||
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
|
||||
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.alwaysShowPercentage = valueAlwaysShowPercentage
|
||||
settings.displayMode = valueDisplayMode
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Always show percentage"
|
||||
checked: valueAlwaysShowPercentage
|
||||
onToggled: checked => valueAlwaysShowPercentage = checked
|
||||
NComboBox {
|
||||
label: "Display mode"
|
||||
description: "Choose how you'd like this value to appear."
|
||||
minimumWidth: 134 * scaling
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "onhover"
|
||||
name: "On Hover"
|
||||
}
|
||||
ListElement {
|
||||
key: "alwaysShow"
|
||||
name: "Always Show"
|
||||
}
|
||||
ListElement {
|
||||
key: "alwaysHide"
|
||||
name: "Always Hide"
|
||||
}
|
||||
}
|
||||
currentKey: valueDisplayMode
|
||||
onSelected: key => valueDisplayMode = key
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,24 +14,41 @@ ColumnLayout {
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local state
|
||||
property bool valueShowDate: widgetData.showDate !== undefined ? widgetData.showDate : widgetMetadata.showDate
|
||||
property string valueDisplayFormat: widgetData.displayFormat !== undefined ? widgetData.displayFormat : widgetMetadata.displayFormat
|
||||
property bool valueUse12h: widgetData.use12HourClock !== undefined ? widgetData.use12HourClock : widgetMetadata.use12HourClock
|
||||
property bool valueShowSeconds: widgetData.showSeconds !== undefined ? widgetData.showSeconds : widgetMetadata.showSeconds
|
||||
property bool valueReverseDayMonth: widgetData.reverseDayMonth !== undefined ? widgetData.reverseDayMonth : widgetMetadata.reverseDayMonth
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.showDate = valueShowDate
|
||||
settings.displayFormat = valueDisplayFormat
|
||||
settings.use12HourClock = valueUse12h
|
||||
settings.showSeconds = valueShowSeconds
|
||||
settings.reverseDayMonth = valueReverseDayMonth
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show date"
|
||||
checked: valueShowDate
|
||||
onToggled: checked => valueShowDate = checked
|
||||
NComboBox {
|
||||
label: "Display Format"
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "time"
|
||||
name: "HH:mm"
|
||||
}
|
||||
ListElement {
|
||||
key: "time-seconds"
|
||||
name: "HH:mm:ss"
|
||||
}
|
||||
ListElement {
|
||||
key: "time-date"
|
||||
name: "HH:mm - Date"
|
||||
}
|
||||
ListElement {
|
||||
key: "time-date-short"
|
||||
name: "HH:mm - Short Date"
|
||||
}
|
||||
}
|
||||
currentKey: valueDisplayFormat
|
||||
onSelected: key => valueDisplayFormat = key
|
||||
minimumWidth: 230 * scaling
|
||||
}
|
||||
|
||||
NToggle {
|
||||
@@ -40,12 +57,6 @@ ColumnLayout {
|
||||
onToggled: checked => valueUse12h = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show seconds"
|
||||
checked: valueShowSeconds
|
||||
onToggled: checked => valueShowSeconds = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Reverse day and month"
|
||||
checked: valueReverseDayMonth
|
||||
|
||||
@@ -19,6 +19,8 @@ ColumnLayout {
|
||||
settings.leftClickExec = leftClickExecInput.text
|
||||
settings.rightClickExec = rightClickExecInput.text
|
||||
settings.middleClickExec = middleClickExecInput.text
|
||||
settings.textCommand = textCommandInput.text
|
||||
settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10)
|
||||
return settings
|
||||
}
|
||||
|
||||
@@ -48,18 +50,16 @@ ColumnLayout {
|
||||
Popup {
|
||||
id: iconPicker
|
||||
modal: true
|
||||
property real panelWidth: {
|
||||
width: {
|
||||
var w = Math.round(Math.max(Screen.width * 0.35, 900) * scaling)
|
||||
w = Math.min(w, Screen.width - Style.marginL * 2)
|
||||
return w
|
||||
}
|
||||
property real panelHeight: {
|
||||
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: panelWidth
|
||||
height: panelHeight
|
||||
anchors.centerIn: Overlay.overlay
|
||||
padding: Style.marginXL * scaling
|
||||
|
||||
@@ -117,10 +117,12 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
// Icon grid
|
||||
ScrollView {
|
||||
NScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AlwaysOn
|
||||
|
||||
GridView {
|
||||
id: grid
|
||||
@@ -228,4 +230,33 @@ ColumnLayout {
|
||||
placeholderText: "Enter command to execute (app or custom script)"
|
||||
text: widgetData.middleClickExec || widgetMetadata.middleClickExec
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Dynamic Text"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: textCommandInput
|
||||
Layout.fillWidth: true
|
||||
label: "Text Command"
|
||||
description: "Shell command to run periodically (first line becomes the text)."
|
||||
placeholderText: "echo \"Hello World\""
|
||||
text: widgetData?.textCommand || widgetMetadata.textCommand
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: textIntervalInput
|
||||
Layout.fillWidth: true
|
||||
label: "Refresh Interval"
|
||||
description: "Interval in milliseconds."
|
||||
placeholderText: String(widgetMetadata.textIntervalMs || 3000)
|
||||
text: widgetData && widgetData.textIntervalMs !== undefined ? String(widgetData.textIntervalMs) : ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Properties to receive data from parent
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local state
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.displayMode = valueDisplayMode
|
||||
return settings
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: "Display mode"
|
||||
description: "Choose how you'd like this value to appear."
|
||||
minimumWidth: 134 * scaling
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "onhover"
|
||||
name: "On Hover"
|
||||
}
|
||||
ListElement {
|
||||
key: "forceOpen"
|
||||
name: "Force Open"
|
||||
}
|
||||
ListElement {
|
||||
key: "alwaysHide"
|
||||
name: "Always Hide"
|
||||
}
|
||||
}
|
||||
currentKey: valueDisplayMode
|
||||
onSelected: key => valueDisplayMode = key
|
||||
}
|
||||
}
|
||||
@@ -14,18 +14,33 @@ ColumnLayout {
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local state
|
||||
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
|
||||
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.alwaysShowPercentage = valueAlwaysShowPercentage
|
||||
settings.displayMode = valueDisplayMode
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Always show percentage"
|
||||
checked: valueAlwaysShowPercentage
|
||||
onToggled: checked => valueAlwaysShowPercentage = checked
|
||||
NComboBox {
|
||||
label: "Display mode"
|
||||
description: "Choose how you'd like this value to appear."
|
||||
minimumWidth: 134 * scaling
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "onhover"
|
||||
name: "On Hover"
|
||||
}
|
||||
ListElement {
|
||||
key: "alwaysShow"
|
||||
name: "Always Show"
|
||||
}
|
||||
ListElement {
|
||||
key: "alwaysHide"
|
||||
name: "Always Hide"
|
||||
}
|
||||
}
|
||||
currentKey: valueDisplayMode
|
||||
onSelected: key => valueDisplayMode = key
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,20 +16,15 @@ ColumnLayout {
|
||||
// Local, editable state for checkboxes
|
||||
property bool valueShowCpuUsage: widgetData.showCpuUsage !== undefined ? widgetData.showCpuUsage : widgetMetadata.showCpuUsage
|
||||
property bool valueShowCpuTemp: widgetData.showCpuTemp !== undefined ? widgetData.showCpuTemp : widgetMetadata.showCpuTemp
|
||||
property bool valueShowGpuTemp: widgetData.showGpuTemp !== undefined ? widgetData.showGpuTemp : (widgetMetadata.showGpuTemp
|
||||
|| false)
|
||||
property bool valueShowMemoryUsage: widgetData.showMemoryUsage !== undefined ? widgetData.showMemoryUsage : widgetMetadata.showMemoryUsage
|
||||
property bool valueShowMemoryAsPercent: widgetData.showMemoryAsPercent
|
||||
!== undefined ? widgetData.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
|
||||
property bool valueShowNetworkStats: widgetData.showNetworkStats
|
||||
!== undefined ? widgetData.showNetworkStats : widgetMetadata.showNetworkStats
|
||||
property bool valueShowMemoryAsPercent: widgetData.showMemoryAsPercent !== undefined ? widgetData.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
|
||||
property bool valueShowNetworkStats: widgetData.showNetworkStats !== undefined ? widgetData.showNetworkStats : widgetMetadata.showNetworkStats
|
||||
property bool valueShowDiskUsage: widgetData.showDiskUsage !== undefined ? widgetData.showDiskUsage : widgetMetadata.showDiskUsage
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.showCpuUsage = valueShowCpuUsage
|
||||
settings.showCpuTemp = valueShowCpuTemp
|
||||
settings.showGpuTemp = valueShowGpuTemp
|
||||
settings.showMemoryUsage = valueShowMemoryUsage
|
||||
settings.showMemoryAsPercent = valueShowMemoryAsPercent
|
||||
settings.showNetworkStats = valueShowNetworkStats
|
||||
@@ -53,14 +48,6 @@ ColumnLayout {
|
||||
onToggled: checked => valueShowCpuTemp = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showGpuTemp
|
||||
Layout.fillWidth: true
|
||||
label: "GPU temperature"
|
||||
checked: valueShowGpuTemp
|
||||
onToggled: checked => valueShowGpuTemp = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: showMemoryUsage
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -14,18 +14,33 @@ ColumnLayout {
|
||||
property var widgetMetadata: null
|
||||
|
||||
// Local state
|
||||
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
|
||||
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.alwaysShowPercentage = valueAlwaysShowPercentage
|
||||
settings.displayMode = valueDisplayMode
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Always show percentage"
|
||||
checked: valueAlwaysShowPercentage
|
||||
onToggled: checked => valueAlwaysShowPercentage = checked
|
||||
NComboBox {
|
||||
label: "Display mode"
|
||||
description: "Choose how you'd like this value to appear."
|
||||
minimumWidth: 134 * scaling
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "onhover"
|
||||
name: "On Hover"
|
||||
}
|
||||
ListElement {
|
||||
key: "alwaysShow"
|
||||
name: "Always Show"
|
||||
}
|
||||
ListElement {
|
||||
key: "alwaysHide"
|
||||
name: "Always Hide"
|
||||
}
|
||||
}
|
||||
currentKey: valueDisplayMode
|
||||
onSelected: key => valueDisplayMode = key
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ ColumnLayout {
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.labelMode = labelModeCombo.currentKey
|
||||
settings.hideUnoccupied = hideUnoccupiedToggle.checked
|
||||
return settings
|
||||
}
|
||||
|
||||
@@ -41,4 +42,12 @@ ColumnLayout {
|
||||
onSelected: key => labelModeCombo.currentKey = key
|
||||
minimumWidth: 200 * scaling
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: hideUnoccupiedToggle
|
||||
label: "Hide unoccupied"
|
||||
description: "Don't display workspaces without windows."
|
||||
checked: widgetData.hideUnoccupied
|
||||
onToggled: checked => hideUnoccupiedToggle.checked = checked
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,16 +11,11 @@ import qs.Widgets
|
||||
NPanel {
|
||||
id: root
|
||||
|
||||
panelWidth: {
|
||||
var w = Math.round(Math.max(screen?.width * 0.4, 1000) * scaling)
|
||||
w = Math.min(w, screen?.width - Style.marginL * 2)
|
||||
return w
|
||||
}
|
||||
panelHeight: {
|
||||
var h = Math.round(Math.max(screen?.height * 0.75, 800) * scaling)
|
||||
h = Math.min(h, screen?.height - Style.barHeight * scaling - Style.marginL * 2)
|
||||
return h
|
||||
}
|
||||
preferredWidth: 1000
|
||||
preferredHeight: 1000
|
||||
preferredWidthRatio: 0.4
|
||||
preferredHeightRatio: 0.75
|
||||
|
||||
panelAnchorHorizontalCenter: true
|
||||
panelAnchorVerticalCenter: true
|
||||
|
||||
@@ -31,13 +26,14 @@ NPanel {
|
||||
About,
|
||||
Audio,
|
||||
Bar,
|
||||
Dock,
|
||||
Hooks,
|
||||
Launcher,
|
||||
Brightness,
|
||||
ColorScheme,
|
||||
Display,
|
||||
General,
|
||||
Network,
|
||||
Notification,
|
||||
ScreenRecorder,
|
||||
Weather,
|
||||
Wallpaper,
|
||||
@@ -72,15 +68,10 @@ NPanel {
|
||||
id: barTab
|
||||
Tabs.BarTab {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: audioTab
|
||||
Tabs.AudioTab {}
|
||||
}
|
||||
Component {
|
||||
id: brightnessTab
|
||||
Tabs.BrightnessTab {}
|
||||
}
|
||||
Component {
|
||||
id: displayTab
|
||||
Tabs.DisplayTab {}
|
||||
@@ -117,6 +108,14 @@ NPanel {
|
||||
id: hooksTab
|
||||
Tabs.HooksTab {}
|
||||
}
|
||||
Component {
|
||||
id: dockTab
|
||||
Tabs.DockTab {}
|
||||
}
|
||||
Component {
|
||||
id: notificationTab
|
||||
Tabs.NotificationTab {}
|
||||
}
|
||||
|
||||
// Order *DOES* matter
|
||||
function updateTabsModel() {
|
||||
@@ -130,6 +129,11 @@ NPanel {
|
||||
"label": "Bar",
|
||||
"icon": "settings-bar",
|
||||
"source": barTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Dock,
|
||||
"label": "Dock",
|
||||
"icon": "settings-dock",
|
||||
"source": dockTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Launcher,
|
||||
"label": "Launcher",
|
||||
@@ -145,16 +149,16 @@ NPanel {
|
||||
"label": "Display",
|
||||
"icon": "settings-display",
|
||||
"source": displayTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Notification,
|
||||
"label": "Notification",
|
||||
"icon": "settings-notification",
|
||||
"source": notificationTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Network,
|
||||
"label": "Network",
|
||||
"icon": "settings-network",
|
||||
"source": networkTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Brightness,
|
||||
"label": "Brightness",
|
||||
"icon": "settings-brightness",
|
||||
"source": brightnessTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Weather,
|
||||
"label": "Weather",
|
||||
@@ -223,8 +227,7 @@ NPanel {
|
||||
if (activeScrollView && activeScrollView.ScrollBar.vertical) {
|
||||
const scrollBar = activeScrollView.ScrollBar.vertical
|
||||
const stepSize = activeScrollView.height * 0.1 // Scroll 10% of viewport
|
||||
scrollBar.position = Math.min(scrollBar.position + stepSize / activeScrollView.contentHeight,
|
||||
1.0 - scrollBar.size)
|
||||
scrollBar.position = Math.min(scrollBar.position + stepSize / activeScrollView.contentHeight, 1.0 - scrollBar.size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,8 +243,7 @@ NPanel {
|
||||
if (activeScrollView && activeScrollView.ScrollBar.vertical) {
|
||||
const scrollBar = activeScrollView.ScrollBar.vertical
|
||||
const pageSize = activeScrollView.height * 0.9 // Scroll 90% of viewport
|
||||
scrollBar.position = Math.min(scrollBar.position + pageSize / activeScrollView.contentHeight,
|
||||
1.0 - scrollBar.size)
|
||||
scrollBar.position = Math.min(scrollBar.position + pageSize / activeScrollView.contentHeight, 1.0 - scrollBar.size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,7 +468,7 @@ NPanel {
|
||||
NIcon {
|
||||
icon: root.tabsModel[currentTabIndex]?.icon
|
||||
color: Color.mPrimary
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
}
|
||||
|
||||
// Main title
|
||||
@@ -482,7 +484,7 @@ NPanel {
|
||||
// Close button
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: "Close"
|
||||
tooltipText: "Close."
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
onClicked: root.close()
|
||||
}
|
||||
@@ -522,11 +524,11 @@ NPanel {
|
||||
anchors.fill: parent
|
||||
pressDelay: 200
|
||||
|
||||
ScrollView {
|
||||
NScrollView {
|
||||
id: scrollView
|
||||
anchors.fill: parent
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
padding: Style.marginL * scaling
|
||||
clip: true
|
||||
|
||||
|
||||
@@ -10,107 +10,108 @@ import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
property string latestVersion: GitHubService.latestVersion
|
||||
property string currentVersion: UpdateService.currentVersion
|
||||
property var contributors: GitHubService.contributors
|
||||
|
||||
NText {
|
||||
text: "Noctalia Shell"
|
||||
font.pointSize: Style.fontSizeXXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.bottomMargin: Style.marginS * scaling
|
||||
NHeader {
|
||||
label: "Noctalia Shell"
|
||||
description: "A sleek and minimal desktop shell thoughtfully crafted for Wayland, built with Quickshell."
|
||||
}
|
||||
|
||||
// Versions
|
||||
GridLayout {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
columns: 2
|
||||
rowSpacing: Style.marginXS * scaling
|
||||
columnSpacing: Style.marginS * scaling
|
||||
RowLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NText {
|
||||
text: "Latest Version:"
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
// Versions
|
||||
GridLayout {
|
||||
columns: 2
|
||||
rowSpacing: Style.marginXS * scaling
|
||||
columnSpacing: Style.marginS * scaling
|
||||
|
||||
NText {
|
||||
text: root.latestVersion
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Installed Version:"
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
NText {
|
||||
text: root.currentVersion
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
}
|
||||
|
||||
// Updater
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.topMargin: Style.marginS * scaling
|
||||
Layout.preferredWidth: Math.round(updateRow.implicitWidth + (Style.marginL * scaling * 2))
|
||||
Layout.preferredHeight: Math.round(Style.barHeight * scaling)
|
||||
radius: Style.radiusL * scaling
|
||||
color: updateArea.containsMouse ? Color.mPrimary : Color.transparent
|
||||
border.color: Color.mPrimary
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
visible: {
|
||||
if (root.latestVersion === "Unknown")
|
||||
return false
|
||||
|
||||
const latest = root.latestVersion.replace("v", "").split(".")
|
||||
const current = root.currentVersion.replace("v", "").split(".")
|
||||
for (var i = 0; i < Math.max(latest.length, current.length); i++) {
|
||||
const l = parseInt(latest[i] || "0")
|
||||
const c = parseInt(current[i] || "0")
|
||||
if (l > c)
|
||||
return true
|
||||
|
||||
if (l < c)
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: updateRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "download"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: updateArea.containsMouse ? Color.mSurface : Color.mPrimary
|
||||
NText {
|
||||
text: "Latest Version:"
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
id: updateText
|
||||
text: "Download latest release"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: updateArea.containsMouse ? Color.mSurface : Color.mPrimary
|
||||
text: root.latestVersion
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Installed Version:"
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: root.currentVersion
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: updateArea
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["xdg-open", "https://github.com/Ly-sec/Noctalia/releases/latest"])
|
||||
// Update button
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.preferredWidth: Math.round(updateRow.implicitWidth + (Style.marginL * scaling * 2))
|
||||
Layout.preferredHeight: Math.round(Style.barHeight * scaling)
|
||||
radius: Style.radiusL * scaling
|
||||
color: updateArea.containsMouse ? Color.mPrimary : Color.transparent
|
||||
border.color: Color.mPrimary
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
visible: {
|
||||
if (root.latestVersion === "Unknown")
|
||||
return false
|
||||
|
||||
const latest = root.latestVersion.replace("v", "").split(".")
|
||||
const current = root.currentVersion.replace("v", "").split(".")
|
||||
for (var i = 0; i < Math.max(latest.length, current.length); i++) {
|
||||
const l = parseInt(latest[i] || "0")
|
||||
const c = parseInt(current[i] || "0")
|
||||
if (l > c)
|
||||
return true
|
||||
|
||||
if (l < c)
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: updateRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "download"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: updateArea.containsMouse ? Color.mSurface : Color.mPrimary
|
||||
}
|
||||
|
||||
NText {
|
||||
id: updateText
|
||||
text: "Download latest release"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: updateArea.containsMouse ? Color.mSurface : Color.mPrimary
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: updateArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["xdg-open", "https://github.com/Ly-sec/Noctalia/releases/latest"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,17 +122,13 @@ ColumnLayout {
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `Shout-out to our ${root.contributors.length} <b>awesome</b> contributors!`
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
NHeader {
|
||||
label: "Contributors"
|
||||
description: `Shout-out to our ${root.contributors.length} <b>awesome</b> contributors!`
|
||||
}
|
||||
|
||||
GridView {
|
||||
id: contributorsGrid
|
||||
|
||||
Layout.topMargin: Style.marginL * scaling
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: cellWidth * 3 // Fixed 3 columns
|
||||
Layout.preferredHeight: {
|
||||
@@ -192,7 +189,7 @@ ColumnLayout {
|
||||
NText {
|
||||
text: modelData.login || "Unknown"
|
||||
font.weight: Style.fontWeightBold
|
||||
color: contributorArea.containsMouse ? Color.mSurface : Color.mOnSurface
|
||||
color: contributorArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
@@ -200,7 +197,7 @@ ColumnLayout {
|
||||
NText {
|
||||
text: (modelData.contributions || 0) + " " + ((modelData.contributions || 0) === 1 ? "commit" : "commits")
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: contributorArea.containsMouse ? Color.mSurface : Color.mOnSurface
|
||||
color: contributorArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,18 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Widgets
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NHeader {
|
||||
label: "Volumes"
|
||||
description: "Configure volume controls and audio levels."
|
||||
}
|
||||
|
||||
property real localVolume: AudioService.volume
|
||||
|
||||
@@ -20,7 +26,7 @@ ColumnLayout {
|
||||
|
||||
// Master Volume
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
@@ -28,37 +34,29 @@ ColumnLayout {
|
||||
description: "System-wide volume level."
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
// Pipewire seems a bit finicky, if we spam too many volume changes it breaks easily
|
||||
// Probably because they have some quick fades in and out to avoid clipping
|
||||
// We use a timer to space out the updates, to avoid lock up
|
||||
Timer {
|
||||
interval: Style.animationFast
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (Math.abs(localVolume - AudioService.volume) >= 0.01) {
|
||||
AudioService.setVolume(localVolume)
|
||||
}
|
||||
// Pipewire seems a bit finicky, if we spam too many volume changes it breaks easily
|
||||
// Probably because they have some quick fades in and out to avoid clipping
|
||||
// We use a timer to space out the updates, to avoid lock up
|
||||
Timer {
|
||||
interval: Style.animationFast
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (Math.abs(localVolume - AudioService.volume) >= 0.01) {
|
||||
AudioService.setVolume(localVolume)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: Settings.data.audio.volumeOverdrive ? 2.0 : 1.0
|
||||
value: localVolume
|
||||
stepSize: 0.01
|
||||
onMoved: {
|
||||
localVolume = value
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: Math.floor(AudioService.volume * 100) + "%"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: Style.marginS * scaling
|
||||
color: Color.mOnSurface
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: Settings.data.audio.volumeOverdrive ? 2.0 : 1.0
|
||||
value: localVolume
|
||||
stepSize: 0.01
|
||||
text: Math.floor(AudioService.volume * 100) + "%"
|
||||
onMoved: {
|
||||
localVolume = value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,7 +65,6 @@ ColumnLayout {
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM * scaling
|
||||
|
||||
NToggle {
|
||||
label: "Mute Audio Output"
|
||||
@@ -83,33 +80,22 @@ ColumnLayout {
|
||||
|
||||
// Input Volume
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
spacing: Style.marginXS * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM * scaling
|
||||
|
||||
NLabel {
|
||||
label: "Input Volume"
|
||||
description: "Microphone input volume level."
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
NSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 1.0
|
||||
value: AudioService.inputVolume
|
||||
stepSize: 0.01
|
||||
onMoved: {
|
||||
AudioService.setInputVolume(value)
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: Math.floor(AudioService.inputVolume * 100) + "%"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: Style.marginS * scaling
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 1.0
|
||||
value: AudioService.inputVolume
|
||||
stepSize: 0.01
|
||||
text: Math.floor(AudioService.inputVolume * 100) + "%"
|
||||
onMoved: value => AudioService.setInputVolume(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +103,6 @@ ColumnLayout {
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM * scaling
|
||||
|
||||
NToggle {
|
||||
label: "Mute Audio Input"
|
||||
@@ -131,7 +116,6 @@ ColumnLayout {
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM * scaling
|
||||
|
||||
NSpinBox {
|
||||
Layout.fillWidth: true
|
||||
@@ -142,9 +126,7 @@ ColumnLayout {
|
||||
value: Settings.data.audio.volumeStep
|
||||
stepSize: 1
|
||||
suffix: "%"
|
||||
onValueChanged: {
|
||||
Settings.data.audio.volumeStep = value
|
||||
}
|
||||
onValueChanged: Settings.data.audio.volumeStep = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,12 +140,9 @@ ColumnLayout {
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
NText {
|
||||
text: "Audio Devices"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
Layout.bottomMargin: Style.marginS * scaling
|
||||
NHeader {
|
||||
label: "Audio Devices"
|
||||
description: "Configure audio input and output devices."
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
@@ -203,7 +182,6 @@ ColumnLayout {
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginL * scaling
|
||||
|
||||
NLabel {
|
||||
label: "Input Device"
|
||||
@@ -234,12 +212,9 @@ ColumnLayout {
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NText {
|
||||
text: "Media Player"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
Layout.bottomMargin: Style.marginS * scaling
|
||||
NHeader {
|
||||
label: "Media Player"
|
||||
description: "Configure your favorite media players."
|
||||
}
|
||||
|
||||
// Preferred player
|
||||
@@ -360,12 +335,9 @@ ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Audio Visualizer"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
Layout.bottomMargin: Style.marginS * scaling
|
||||
NHeader {
|
||||
label: "Audio Visualizer"
|
||||
description: "Customize visual effects that respond to audio playback."
|
||||
}
|
||||
|
||||
// AudioService Visualizer section
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
@@ -8,6 +9,20 @@ import qs.Modules.SettingsPanel.Bar
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
// Helper functions to update arrays immutably
|
||||
function addMonitor(list, name) {
|
||||
const arr = (list || []).slice()
|
||||
if (!arr.includes(name))
|
||||
arr.push(name)
|
||||
return arr
|
||||
}
|
||||
function removeMonitor(list, name) {
|
||||
return (list || []).filter(function (n) {
|
||||
return n !== name
|
||||
})
|
||||
}
|
||||
|
||||
// Handler for drag start - disables panel background clicks
|
||||
function handleDragStart() {
|
||||
@@ -25,65 +40,153 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
NHeader {
|
||||
label: "Appearance"
|
||||
description: "Configure bar appearance and positioning."
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: "Bar Position"
|
||||
description: "Choose where to place the bar on the screen."
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "top"
|
||||
name: "Top"
|
||||
}
|
||||
ListElement {
|
||||
key: "bottom"
|
||||
name: "Bottom"
|
||||
}
|
||||
RowLayout {
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: "Bar Position"
|
||||
description: "Choose where to place the bar on the screen."
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "top"
|
||||
name: "Top"
|
||||
}
|
||||
ListElement {
|
||||
key: "bottom"
|
||||
name: "Bottom"
|
||||
}
|
||||
ListElement {
|
||||
key: "left"
|
||||
name: "Left"
|
||||
}
|
||||
ListElement {
|
||||
key: "right"
|
||||
name: "Right"
|
||||
}
|
||||
currentKey: Settings.data.bar.position
|
||||
onSelected: key => Settings.data.bar.position = key
|
||||
}
|
||||
currentKey: Settings.data.bar.position
|
||||
onSelected: key => Settings.data.bar.position = key
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: "Background Opacity"
|
||||
description: "Adjust the background opacity of the bar."
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
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: "Floating Bar"
|
||||
description: "Make the bar float with rounded corners and margins. Screen corners will move to screen edges."
|
||||
checked: Settings.data.bar.floating
|
||||
onToggled: checked => Settings.data.bar.floating = checked
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Background Opacity"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
// Floating bar options - only show when floating is enabled
|
||||
ColumnLayout {
|
||||
visible: Settings.data.bar.floating
|
||||
spacing: Style.marginS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Adjust the background opacity of the bar."
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
NLabel {
|
||||
label: "Margins"
|
||||
description: "Adjust the margins around the floating bar."
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
NSlider {
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
|
||||
NText {
|
||||
text: "Vertical"
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.bar.backgroundOpacity
|
||||
onMoved: Settings.data.bar.backgroundOpacity = value
|
||||
cutoutColor: Color.mSurface
|
||||
value: Settings.data.bar.marginVertical
|
||||
onMoved: value => Settings.data.bar.marginVertical = value
|
||||
text: Math.round(Settings.data.bar.marginVertical * 100) + "%"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
|
||||
NText {
|
||||
text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: Style.marginS * scaling
|
||||
color: Color.mOnSurface
|
||||
text: "Horizontal"
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.bar.marginHorizontal
|
||||
onMoved: value => Settings.data.bar.marginHorizontal = value
|
||||
text: Math.round(Settings.data.bar.marginHorizontal * 100) + "%"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Monitor Configuration
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Monitors Configuration"
|
||||
description: "Choose which monitors should display the bar."
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: `${modelData.name || "Unknown"}${modelData.model ? `: ${modelData.model}` : ""}`
|
||||
description: `${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})`
|
||||
checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.bar.monitors = addMonitor(Settings.data.bar.monitors, modelData.name)
|
||||
} else {
|
||||
Settings.data.bar.monitors = removeMonitor(Settings.data.bar.monitors, modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,20 +202,9 @@ ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Widgets Positioning"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
Layout.bottomMargin: Style.marginS * scaling
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Drag and drop widgets to reorder them within each section, or use the add/remove buttons to manage widgets."
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
NHeader {
|
||||
label: "Widgets Positioning"
|
||||
description: "Drag and drop widgets to reorder them within each section, or use the add/remove buttons to manage widgets."
|
||||
}
|
||||
|
||||
// Bar Sections
|
||||
@@ -201,8 +293,7 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
function _reorderWidgetInSection(section, fromIndex, toIndex) {
|
||||
if (fromIndex >= 0 && fromIndex < Settings.data.bar.widgets[section].length && toIndex >= 0
|
||||
&& toIndex < Settings.data.bar.widgets[section].length) {
|
||||
if (fromIndex >= 0 && fromIndex < Settings.data.bar.widgets[section].length && toIndex >= 0 && toIndex < Settings.data.bar.widgets[section].length) {
|
||||
|
||||
// Create a new array to avoid modifying the original
|
||||
var newArray = Settings.data.bar.widgets[section].slice()
|
||||
|
||||
@@ -1,341 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
// Time dropdown options (00:00 .. 23:30)
|
||||
ListModel {
|
||||
id: timeOptions
|
||||
}
|
||||
Component.onCompleted: {
|
||||
for (var h = 0; h < 24; h++) {
|
||||
for (var m = 0; m < 60; m += 30) {
|
||||
var hh = ("0" + h).slice(-2)
|
||||
var mm = ("0" + m).slice(-2)
|
||||
var key = hh + ":" + mm
|
||||
timeOptions.append({
|
||||
"key": key,
|
||||
"name": key
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for wlsunset availability when enabling Night Light
|
||||
Process {
|
||||
id: wlsunsetCheck
|
||||
command: ["which", "wlsunset"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
Settings.data.nightLight.enabled = true
|
||||
NightLightService.apply()
|
||||
ToastService.showNotice("Night Light", "Enabled")
|
||||
} else {
|
||||
Settings.data.nightLight.enabled = false
|
||||
ToastService.showWarning("Night Light", "wlsunset not installed")
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
// Brightness Step Section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NSpinBox {
|
||||
Layout.fillWidth: true
|
||||
label: "Brightness Step Size"
|
||||
description: "Adjust the step size for brightness changes (scroll wheel, keyboard shortcuts)."
|
||||
minimum: 1
|
||||
maximum: 50
|
||||
value: Settings.data.brightness.brightnessStep
|
||||
stepSize: 1
|
||||
suffix: "%"
|
||||
onValueChanged: {
|
||||
Settings.data.brightness.brightnessStep = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Monitor Overview Section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NLabel {
|
||||
label: "Monitors Brightness Control"
|
||||
description: "Current brightness levels for all detected monitors."
|
||||
}
|
||||
|
||||
// Single monitor display using the same data source as the bar icon
|
||||
Repeater {
|
||||
model: BrightnessService.monitors
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
radius: Style.radiusM * scaling
|
||||
color: Color.mSurface
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling
|
||||
|
||||
ColumnLayout {
|
||||
id: contentCol
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL * scaling
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NText {
|
||||
text: `${model.modelData.name} [${model.modelData.model}]`
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: model.method
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NText {
|
||||
text: "Brightness:"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 1
|
||||
value: model.brightness
|
||||
stepSize: 0.05
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
var monitor = BrightnessService.getMonitorForScreen(model.modelData)
|
||||
monitor.setBrightness(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: Math.round(model.brightness * 100) + "%"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mPrimary
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Night Light Section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Night Light"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Reduce blue light emission to help you sleep better and reduce eye strain."
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Enable Night Light"
|
||||
description: "Apply a warm color filter to reduce blue light emission."
|
||||
checked: Settings.data.nightLight.enabled
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
// Verify wlsunset exists before enabling
|
||||
wlsunsetCheck.running = true
|
||||
} else {
|
||||
Settings.data.nightLight.enabled = false
|
||||
Settings.data.nightLight.forced = false
|
||||
NightLightService.apply()
|
||||
ToastService.showNotice("Night Light", "Disabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Temperature
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
NLabel {
|
||||
label: "Color temperature"
|
||||
description: "Choose two temperatures in Kelvin."
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: Settings.data.nightLight.enabled
|
||||
spacing: Style.marginM * scaling
|
||||
Layout.fillWidth: false
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
NText {
|
||||
text: "Night"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
text: Settings.data.nightLight.nightTemp
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
onEditingFinished: {
|
||||
var nightTemp = parseInt(text)
|
||||
var dayTemp = parseInt(Settings.data.nightLight.dayTemp)
|
||||
if (!isNaN(nightTemp) && !isNaN(dayTemp)) {
|
||||
// Clamp value between [1000 .. (dayTemp-500)]
|
||||
var clampedValue = Math.min(dayTemp - 500, Math.max(1000, nightTemp))
|
||||
text = Settings.data.nightLight.nightTemp = clampedValue.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {}
|
||||
|
||||
NText {
|
||||
text: "Day"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
NTextInput {
|
||||
text: Settings.data.nightLight.dayTemp
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
onEditingFinished: {
|
||||
var dayTemp = parseInt(text)
|
||||
var nightTemp = parseInt(Settings.data.nightLight.nightTemp)
|
||||
if (!isNaN(nightTemp) && !isNaN(dayTemp)) {
|
||||
// Clamp value between [(nightTemp+500) .. 6500]
|
||||
var clampedValue = Math.max(nightTemp + 500, Math.min(6500, dayTemp))
|
||||
text = Settings.data.nightLight.dayTemp = clampedValue.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Automatic Scheduling"
|
||||
description: `Based on the sunset and sunrise time in <i>${LocationService.stableName}</i> - recommended.`
|
||||
checked: Settings.data.nightLight.autoSchedule
|
||||
onToggled: checked => Settings.data.nightLight.autoSchedule = checked
|
||||
visible: Settings.data.nightLight.enabled
|
||||
}
|
||||
|
||||
// Schedule settings
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS * scaling
|
||||
visible: Settings.data.nightLight.enabled && !Settings.data.nightLight.autoSchedule
|
||||
&& !Settings.data.nightLight.forced
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NLabel {
|
||||
label: "Manual Scheduling"
|
||||
}
|
||||
|
||||
Item {// add a little more spacing
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Sunrise Time"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: timeOptions
|
||||
currentKey: Settings.data.nightLight.manualSunrise
|
||||
placeholder: "Select start time"
|
||||
onSelected: key => Settings.data.nightLight.manualSunrise = key
|
||||
minimumWidth: 120 * scaling
|
||||
}
|
||||
|
||||
Item {// add a little more spacing
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Sunset Time"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
NComboBox {
|
||||
model: timeOptions
|
||||
currentKey: Settings.data.nightLight.manualSunset
|
||||
placeholder: "Select stop time"
|
||||
onSelected: key => Settings.data.nightLight.manualSunset = key
|
||||
minimumWidth: 120 * scaling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force activation toggle
|
||||
NToggle {
|
||||
label: "Force activation"
|
||||
description: "Immediately apply night temperature without scheduling or fade."
|
||||
checked: Settings.data.nightLight.forced
|
||||
onToggled: checked => {
|
||||
Settings.data.nightLight.forced = checked
|
||||
if (checked && !Settings.data.nightLight.enabled) {
|
||||
// Ensure enabled when forcing
|
||||
wlsunsetCheck.running = true
|
||||
} else {
|
||||
NightLightService.apply()
|
||||
}
|
||||
}
|
||||
visible: Settings.data.nightLight.enabled
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
// Cache for scheme JSON (can be flat or {dark, light})
|
||||
property var schemeColorsCache: ({})
|
||||
@@ -105,39 +105,39 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
// Main Toggles - Dark Mode / Matugen
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
NHeader {
|
||||
label: "Behavior"
|
||||
description: "Main settings for Noctalia's colors."
|
||||
}
|
||||
|
||||
// Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants)
|
||||
NToggle {
|
||||
label: "Dark Mode"
|
||||
description: Settings.data.colorSchemes.useWallpaperColors ? "Generate dark theme colors when using Matugen." : "Use a dark variant if available."
|
||||
checked: Settings.data.colorSchemes.darkMode
|
||||
enabled: true
|
||||
onToggled: checked => Settings.data.colorSchemes.darkMode = checked
|
||||
}
|
||||
// Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants)
|
||||
NToggle {
|
||||
label: "Dark Mode"
|
||||
description: Settings.data.colorSchemes.useWallpaperColors ? "Generate dark theme colors when using Matugen." : "Use a dark variant if available."
|
||||
checked: Settings.data.colorSchemes.darkMode
|
||||
enabled: true
|
||||
onToggled: checked => Settings.data.colorSchemes.darkMode = checked
|
||||
}
|
||||
|
||||
// Use Matugen
|
||||
NToggle {
|
||||
label: "Enable Matugen"
|
||||
description: "Automatically generate colors based on your active wallpaper."
|
||||
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("Matugen", "Disabled")
|
||||
// Use Matugen
|
||||
NToggle {
|
||||
label: "Enable Matugen"
|
||||
description: "Automatically generate colors based on your active wallpaper."
|
||||
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("Matugen", "Disabled")
|
||||
|
||||
if (Settings.data.colorSchemes.predefinedScheme) {
|
||||
if (Settings.data.colorSchemes.predefinedScheme) {
|
||||
|
||||
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
|
||||
}
|
||||
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
@@ -151,19 +151,9 @@ ColumnLayout {
|
||||
spacing: Style.marginM * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Predefined Color Schemes"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "To use these color schemes, you must turn off Matugen. With Matugen enabled, colors are automatically generated from your wallpaper."
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
NHeader {
|
||||
label: "Predefined Color Schemes"
|
||||
description: "To use these color schemes, you must turn off Matugen. With Matugen enabled, colors are automatically generated from your wallpaper."
|
||||
}
|
||||
|
||||
// Color Schemes Grid
|
||||
@@ -186,9 +176,7 @@ ColumnLayout {
|
||||
radius: Style.radiusM * scaling
|
||||
color: getSchemeColor(modelData, "mSurface")
|
||||
border.width: Math.max(1, Style.borderL * scaling)
|
||||
border.color: (!Settings.data.colorSchemes.useWallpaperColors
|
||||
&& (Settings.data.colorSchemes.predefinedScheme === modelData.split("/").pop().replace(
|
||||
".json", ""))) ? Color.mPrimary : Color.mOutline
|
||||
border.color: (!Settings.data.colorSchemes.useWallpaperColors && (Settings.data.colorSchemes.predefinedScheme === modelData.split("/").pop().replace(".json", ""))) ? Color.mSecondary : Color.mOutline
|
||||
scale: root.cardScaleLow
|
||||
|
||||
// Mouse area for selection
|
||||
@@ -281,23 +269,21 @@ ColumnLayout {
|
||||
|
||||
// Selection indicator (Checkmark)
|
||||
Rectangle {
|
||||
visible: !Settings.data.colorSchemes.useWallpaperColors
|
||||
&& (Settings.data.colorSchemes.predefinedScheme === schemePath.split("/").pop().replace(".json",
|
||||
""))
|
||||
visible: !Settings.data.colorSchemes.useWallpaperColors && (Settings.data.colorSchemes.predefinedScheme === schemePath.split("/").pop().replace(".json", ""))
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Style.marginS * scaling
|
||||
width: 24 * scaling
|
||||
height: 24 * scaling
|
||||
width: 28 * scaling
|
||||
height: 28 * scaling
|
||||
radius: width * 0.5
|
||||
color: Color.mPrimary
|
||||
color: Color.mSecondary
|
||||
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
text: "✓"
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
NIcon {
|
||||
icon: "check"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnPrimary
|
||||
color: Color.mOnSecondary
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,146 +318,187 @@ ColumnLayout {
|
||||
visible: Settings.data.colorSchemes.useWallpaperColors
|
||||
}
|
||||
|
||||
// Matugen template toggles (moved from MatugenTab)
|
||||
// Matugen template toggles organized by category
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
visible: Settings.data.colorSchemes.useWallpaperColors
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
// UI Components
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
label: "UI"
|
||||
description: "Desktop environment and UI toolkit theming."
|
||||
defaultExpanded: false
|
||||
|
||||
NText {
|
||||
text: "Matugen Templates"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
NCheckbox {
|
||||
label: "GTK 4 (libadwaita)"
|
||||
description: "Write ~/.config/gtk-4.0/gtk.css"
|
||||
checked: Settings.data.matugen.gtk4
|
||||
onToggled: checked => {
|
||||
Settings.data.matugen.gtk4 = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Select which external components Matugen should apply theming to."
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
NCheckbox {
|
||||
label: "GTK 3"
|
||||
description: "Write ~/.config/gtk-3.0/gtk.css"
|
||||
checked: Settings.data.matugen.gtk3
|
||||
onToggled: checked => {
|
||||
Settings.data.matugen.gtk3 = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Qt6ct"
|
||||
description: "Write ~/.config/qt6ct/colors/noctalia.conf"
|
||||
checked: Settings.data.matugen.qt6
|
||||
onToggled: checked => {
|
||||
Settings.data.matugen.qt6 = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Qt5ct"
|
||||
description: "Write ~/.config/qt5ct/colors/noctalia.conf"
|
||||
checked: Settings.data.matugen.qt5
|
||||
onToggled: checked => {
|
||||
Settings.data.matugen.qt5 = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "GTK 4 (libadwaita)"
|
||||
description: "Write ~/.config/gtk-4.0/gtk.css"
|
||||
checked: Settings.data.matugen.gtk4
|
||||
onToggled: checked => {
|
||||
Settings.data.matugen.gtk4 = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "GTK 3"
|
||||
description: "Write ~/.config/gtk-3.0/gtk.css"
|
||||
checked: Settings.data.matugen.gtk3
|
||||
onToggled: checked => {
|
||||
Settings.data.matugen.gtk3 = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Qt6ct"
|
||||
description: "Write ~/.config/qt6ct/colors/noctalia.conf"
|
||||
checked: Settings.data.matugen.qt6
|
||||
onToggled: checked => {
|
||||
Settings.data.matugen.qt6 = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Qt5ct"
|
||||
description: "Write ~/.config/qt5ct/colors/noctalia.conf"
|
||||
checked: Settings.data.matugen.qt5
|
||||
onToggled: checked => {
|
||||
Settings.data.matugen.qt5 = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Kitty"
|
||||
description: "Write ~/.config/kitty/themes/noctalia.conf and reload"
|
||||
checked: Settings.data.matugen.kitty
|
||||
onToggled: checked => {
|
||||
Settings.data.matugen.kitty = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Ghostty"
|
||||
description: "Write ~/.config/ghostty/themes/noctalia and reload"
|
||||
checked: Settings.data.matugen.ghostty
|
||||
onToggled: checked => {
|
||||
Settings.data.matugen.ghostty = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Foot"
|
||||
description: "Write ~/.config/foot/themes/noctalia and reload"
|
||||
checked: Settings.data.matugen.foot
|
||||
onToggled: checked => {
|
||||
Settings.data.matugen.foot = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Fuzzel"
|
||||
description: "Write ~/.config/fuzzel/themes/noctalia and reload"
|
||||
checked: Settings.data.matugen.fuzzel
|
||||
onToggled: checked => {
|
||||
Settings.data.matugen.fuzzel = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Vesktop"
|
||||
description: "Write ~/.config/vesktop/themes/noctalia.theme.css"
|
||||
checked: Settings.data.matugen.vesktop
|
||||
onToggled: checked => {
|
||||
Settings.data.matugen.vesktop = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
// Terminal Emulators
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM * scaling
|
||||
Layout.bottomMargin: Style.marginM * scaling
|
||||
label: "Terminal"
|
||||
description: "Terminal emulator theming."
|
||||
defaultExpanded: false
|
||||
|
||||
NCheckbox {
|
||||
label: "Kitty"
|
||||
description: ProgramCheckerService.kittyAvailable ? "Write ~/.config/kitty/themes/noctalia.conf and reload" : "Requires kitty terminal to be installed"
|
||||
checked: Settings.data.matugen.kitty
|
||||
enabled: ProgramCheckerService.kittyAvailable
|
||||
opacity: ProgramCheckerService.kittyAvailable ? 1.0 : 0.6
|
||||
onToggled: checked => {
|
||||
if (ProgramCheckerService.kittyAvailable) {
|
||||
Settings.data.matugen.kitty = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Ghostty"
|
||||
description: ProgramCheckerService.ghosttyAvailable ? "Write ~/.config/ghostty/themes/noctalia and reload" : "Requires ghostty terminal to be installed"
|
||||
checked: Settings.data.matugen.ghostty
|
||||
enabled: ProgramCheckerService.ghosttyAvailable
|
||||
opacity: ProgramCheckerService.ghosttyAvailable ? 1.0 : 0.6
|
||||
onToggled: checked => {
|
||||
if (ProgramCheckerService.ghosttyAvailable) {
|
||||
Settings.data.matugen.ghostty = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Foot"
|
||||
description: ProgramCheckerService.footAvailable ? "Write ~/.config/foot/themes/noctalia and reload" : "Requires foot terminal to be installed"
|
||||
checked: Settings.data.matugen.foot
|
||||
enabled: ProgramCheckerService.footAvailable
|
||||
opacity: ProgramCheckerService.footAvailable ? 1.0 : 0.6
|
||||
onToggled: checked => {
|
||||
if (ProgramCheckerService.footAvailable) {
|
||||
Settings.data.matugen.foot = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "User Templates"
|
||||
description: "Enable user-defined Matugen config from ~/.config/matugen/config.toml"
|
||||
checked: Settings.data.matugen.enableUserTemplates
|
||||
onToggled: checked => {
|
||||
Settings.data.matugen.enableUserTemplates = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
// Applications
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
label: "Programs"
|
||||
description: "Application-specific theming."
|
||||
defaultExpanded: false
|
||||
|
||||
NCheckbox {
|
||||
label: "Fuzzel"
|
||||
description: ProgramCheckerService.fuzzelAvailable ? "Write ~/.config/fuzzel/themes/noctalia and reload" : "Requires fuzzel launcher to be installed"
|
||||
checked: Settings.data.matugen.fuzzel
|
||||
enabled: ProgramCheckerService.fuzzelAvailable
|
||||
opacity: ProgramCheckerService.fuzzelAvailable ? 1.0 : 0.6
|
||||
onToggled: checked => {
|
||||
if (ProgramCheckerService.fuzzelAvailable) {
|
||||
Settings.data.matugen.fuzzel = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Vesktop"
|
||||
description: ProgramCheckerService.vesktopAvailable ? "Write ~/.config/vesktop/themes/noctalia.theme.css" : "Requires vesktop Discord client to be installed"
|
||||
checked: Settings.data.matugen.vesktop
|
||||
enabled: ProgramCheckerService.vesktopAvailable
|
||||
opacity: ProgramCheckerService.vesktopAvailable ? 1.0 : 0.6
|
||||
onToggled: checked => {
|
||||
if (ProgramCheckerService.vesktopAvailable) {
|
||||
Settings.data.matugen.vesktop = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NCheckbox {
|
||||
label: "Pywalfox (Firefox)"
|
||||
description: ProgramCheckerService.pywalfoxAvailable ? "Write ~/.cache/wal/colors.json and run pywalfox update" : "Requires pywalfox package to be installed"
|
||||
checked: Settings.data.matugen.pywalfox
|
||||
enabled: ProgramCheckerService.pywalfoxAvailable
|
||||
opacity: ProgramCheckerService.pywalfoxAvailable ? 1.0 : 0.6
|
||||
onToggled: checked => {
|
||||
if (ProgramCheckerService.pywalfoxAvailable) {
|
||||
Settings.data.matugen.pywalfox = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
NCollapsible {
|
||||
Layout.fillWidth: true
|
||||
label: "Misc"
|
||||
description: "Additional configuration options."
|
||||
defaultExpanded: false
|
||||
|
||||
NCheckbox {
|
||||
label: "User Templates"
|
||||
description: "Enable user-defined Matugen config from ~/.config/matugen/config.toml"
|
||||
checked: Settings.data.matugen.enableUserTemplates
|
||||
onToggled: checked => {
|
||||
Settings.data.matugen.enableUserTemplates = checked
|
||||
if (Settings.data.colorSchemes.useWallpaperColors)
|
||||
MatugenService.generateFromWallpaper()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
@@ -9,49 +10,68 @@ import qs.Widgets
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
// Helper functions to update arrays immutably
|
||||
function addMonitor(list, name) {
|
||||
const arr = (list || []).slice()
|
||||
if (!arr.includes(name))
|
||||
arr.push(name)
|
||||
return arr
|
||||
// Time dropdown options (00:00 .. 23:30)
|
||||
ListModel {
|
||||
id: timeOptions
|
||||
}
|
||||
function removeMonitor(list, name) {
|
||||
return (list || []).filter(function (n) {
|
||||
return n !== name
|
||||
})
|
||||
Component.onCompleted: {
|
||||
for (var h = 0; h < 24; h++) {
|
||||
for (var m = 0; m < 60; m += 30) {
|
||||
var hh = ("0" + h).slice(-2)
|
||||
var mm = ("0" + m).slice(-2)
|
||||
var key = hh + ":" + mm
|
||||
timeOptions.append({
|
||||
"key": key,
|
||||
"name": key
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Monitor-specific configuration"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
// Check for wlsunset availability when enabling Night Light
|
||||
Process {
|
||||
id: wlsunsetCheck
|
||||
command: ["which", "wlsunset"]
|
||||
running: false
|
||||
|
||||
onExited: function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
Settings.data.nightLight.enabled = true
|
||||
NightLightService.apply()
|
||||
ToastService.showNotice("Night Light", "Enabled")
|
||||
} else {
|
||||
Settings.data.nightLight.enabled = false
|
||||
ToastService.showWarning("Night Light", "wlsunset not installed")
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Bars and notifications appear on all displays by default. Choose specific displays below to limit where they're shown."
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NHeader {
|
||||
label: "Monitor-specific configuration"
|
||||
description: "Configure scaling and brightness settings individually for each connected display."
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.topMargin: Style.marginL * scaling
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: 550 * scaling
|
||||
implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling
|
||||
radius: Style.radiusM * scaling
|
||||
color: Color.mSurface
|
||||
color: Color.mSurfaceVariant
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling
|
||||
|
||||
property real localScaling: ScalingService.getScreenScale(modelData)
|
||||
property var brightnessMonitor: BrightnessService.getMonitorForScreen(modelData)
|
||||
|
||||
Connections {
|
||||
target: ScalingService
|
||||
function onScaleChanged(screenName, scale) {
|
||||
@@ -68,122 +88,100 @@ ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
|
||||
NText {
|
||||
text: (modelData.name || "Unknown")
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
text: (`${modelData.name}: ${modelData.model}` || "Unknown")
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `Resolution: ${modelData.width}x${modelData.height} - Position: (${modelData.x}, ${modelData.y})`
|
||||
text: `Resolution: ${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})`
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Scale
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
spacing: Style.marginS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: "Bar"
|
||||
description: "Enable the bar on this monitor."
|
||||
checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.bar.monitors = addMonitor(Settings.data.bar.monitors, modelData.name)
|
||||
} else {
|
||||
Settings.data.bar.monitors = removeMonitor(Settings.data.bar.monitors, modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: "Notifications"
|
||||
description: "Enable notifications on this monitor."
|
||||
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.notifications.monitors = addMonitor(Settings.data.notifications.monitors,
|
||||
modelData.name)
|
||||
} else {
|
||||
Settings.data.notifications.monitors = removeMonitor(Settings.data.notifications.monitors,
|
||||
modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: "Dock"
|
||||
description: "Enable the dock on this monitor."
|
||||
checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.dock.monitors = addMonitor(Settings.data.dock.monitors, modelData.name)
|
||||
} else {
|
||||
Settings.data.dock.monitors = removeMonitor(Settings.data.dock.monitors, modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
RowLayout {
|
||||
spacing: Style.marginM * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Scale"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
NText {
|
||||
text: "Scale the user interface on this monitor."
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: `${Math.round(localScaling * 100)}%`
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.minimumWidth: 50 * scaling
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
NText {
|
||||
text: "Scale"
|
||||
Layout.preferredWidth: 80 * scaling
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
NValueSlider {
|
||||
id: scaleSlider
|
||||
from: 0.7
|
||||
to: 1.8
|
||||
stepSize: 0.01
|
||||
value: localScaling
|
||||
onPressedChanged: (pressed, value) => ScalingService.setScreenScale(modelData, value)
|
||||
text: `${Math.round(localScaling * 100)}%`
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NSlider {
|
||||
id: scaleSlider
|
||||
from: 0.7
|
||||
to: 1.8
|
||||
stepSize: 0.01
|
||||
value: localScaling
|
||||
onPressedChanged: ScalingService.setScreenScale(modelData, value)
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: 150 * scaling
|
||||
}
|
||||
// Reset button container
|
||||
Item {
|
||||
Layout.preferredWidth: 40 * scaling
|
||||
Layout.preferredHeight: 30 * scaling
|
||||
|
||||
NIconButton {
|
||||
icon: "refresh"
|
||||
sizeRatio: 0.8
|
||||
tooltipText: "Reset scaling"
|
||||
onClicked: ScalingService.setScreenScale(modelData, 1.0)
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Brightness
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
visible: brightnessMonitor !== undefined && brightnessMonitor !== null
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NText {
|
||||
text: "Brightness"
|
||||
Layout.preferredWidth: 80 * scaling
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 1
|
||||
value: brightnessMonitor ? brightnessMonitor.brightness : 0.5
|
||||
stepSize: 0.01
|
||||
onPressedChanged: (pressed, value) => brightnessMonitor.setBrightness(value)
|
||||
text: brightnessMonitor ? Math.round(brightnessMonitor.brightness * 100) + "%" : "N/A"
|
||||
}
|
||||
|
||||
// Empty container to match scale row layout
|
||||
Item {
|
||||
Layout.preferredWidth: 40 * scaling
|
||||
Layout.preferredHeight: 30 * scaling
|
||||
|
||||
// Method text positioned in the button area
|
||||
NText {
|
||||
text: brightnessMonitor ? brightnessMonitor.method : ""
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,4 +190,216 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Brightness Section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Brightness"
|
||||
description: "Adjust brightness related settings."
|
||||
}
|
||||
|
||||
// Brightness Step Section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NSpinBox {
|
||||
Layout.fillWidth: true
|
||||
label: "Brightness Step Size"
|
||||
description: "Adjust the step size for brightness changes (scroll wheel and keyboard shortcuts)."
|
||||
minimum: 1
|
||||
maximum: 50
|
||||
value: Settings.data.brightness.brightnessStep
|
||||
stepSize: 1
|
||||
suffix: "%"
|
||||
onValueChanged: Settings.data.brightness.brightnessStep = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Night Light Section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Night Light"
|
||||
description: "Reduce blue light emission to help you sleep better and reduce eye strain."
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Enable Night Light"
|
||||
description: "Apply a warm color filter to reduce blue light emission."
|
||||
checked: Settings.data.nightLight.enabled
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
// Verify wlsunset exists before enabling
|
||||
wlsunsetCheck.running = true
|
||||
} else {
|
||||
Settings.data.nightLight.enabled = false
|
||||
Settings.data.nightLight.forced = false
|
||||
NightLightService.apply()
|
||||
ToastService.showNotice("Night Light", "Disabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Temperature
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS * scaling
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
NLabel {
|
||||
label: "Color temperature"
|
||||
description: "Choose two temperatures in Kelvin."
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: Settings.data.nightLight.enabled
|
||||
spacing: Style.marginM * scaling
|
||||
Layout.fillWidth: false
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
NText {
|
||||
text: "Night"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
text: Settings.data.nightLight.nightTemp
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
onEditingFinished: {
|
||||
var nightTemp = parseInt(text)
|
||||
var dayTemp = parseInt(Settings.data.nightLight.dayTemp)
|
||||
if (!isNaN(nightTemp) && !isNaN(dayTemp)) {
|
||||
// Clamp value between [1000 .. (dayTemp-500)]
|
||||
var clampedValue = Math.min(dayTemp - 500, Math.max(1000, nightTemp))
|
||||
text = Settings.data.nightLight.nightTemp = clampedValue.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {}
|
||||
|
||||
NText {
|
||||
text: "Day"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
NTextInput {
|
||||
text: Settings.data.nightLight.dayTemp
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
onEditingFinished: {
|
||||
var dayTemp = parseInt(text)
|
||||
var nightTemp = parseInt(Settings.data.nightLight.nightTemp)
|
||||
if (!isNaN(nightTemp) && !isNaN(dayTemp)) {
|
||||
// Clamp value between [(nightTemp+500) .. 6500]
|
||||
var clampedValue = Math.max(nightTemp + 500, Math.min(6500, dayTemp))
|
||||
text = Settings.data.nightLight.dayTemp = clampedValue.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Automatic Scheduling"
|
||||
description: `Based on the sunset and sunrise time in <i>${LocationService.stableName}</i> - recommended.`
|
||||
checked: Settings.data.nightLight.autoSchedule
|
||||
onToggled: checked => Settings.data.nightLight.autoSchedule = checked
|
||||
visible: Settings.data.nightLight.enabled
|
||||
}
|
||||
|
||||
// Schedule settings
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS * scaling
|
||||
visible: Settings.data.nightLight.enabled && !Settings.data.nightLight.autoSchedule && !Settings.data.nightLight.forced
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NLabel {
|
||||
label: "Manual Scheduling"
|
||||
}
|
||||
|
||||
Item {// add a little more spacing
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Sunrise Time"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: timeOptions
|
||||
currentKey: Settings.data.nightLight.manualSunrise
|
||||
placeholder: "Select start time"
|
||||
onSelected: key => Settings.data.nightLight.manualSunrise = key
|
||||
minimumWidth: 120 * scaling
|
||||
}
|
||||
|
||||
Item {// add a little more spacing
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Sunset Time"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
NComboBox {
|
||||
model: timeOptions
|
||||
currentKey: Settings.data.nightLight.manualSunset
|
||||
placeholder: "Select stop time"
|
||||
onSelected: key => Settings.data.nightLight.manualSunset = key
|
||||
minimumWidth: 120 * scaling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force activation toggle
|
||||
NToggle {
|
||||
label: "Force activation"
|
||||
description: "Immediately apply night temperature without scheduling or fade."
|
||||
checked: Settings.data.nightLight.forced
|
||||
onToggled: checked => {
|
||||
Settings.data.nightLight.forced = checked
|
||||
if (checked && !Settings.data.nightLight.enabled) {
|
||||
// Ensure enabled when forcing
|
||||
wlsunsetCheck.running = true
|
||||
} else {
|
||||
NightLightService.apply()
|
||||
}
|
||||
}
|
||||
visible: Settings.data.nightLight.enabled
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
}
|
||||
|
||||
122
Modules/SettingsPanel/Tabs/DockTab.qml
Normal file
@@ -0,0 +1,122 @@
|
||||
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 * scaling
|
||||
|
||||
// Helper functions to update arrays immutably
|
||||
function addMonitor(list, name) {
|
||||
const arr = (list || []).slice()
|
||||
if (!arr.includes(name))
|
||||
arr.push(name)
|
||||
return arr
|
||||
}
|
||||
function removeMonitor(list, name) {
|
||||
return (list || []).filter(function (n) {
|
||||
return n !== name
|
||||
})
|
||||
}
|
||||
|
||||
NHeader {
|
||||
label: "Appearance"
|
||||
description: "Configure dock behavior and appearance."
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Auto-hide"
|
||||
description: "Automatically hide when not in use."
|
||||
checked: Settings.data.dock.autoHide
|
||||
onToggled: checked => Settings.data.dock.autoHide = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Exclusive Zone"
|
||||
description: "Ensure windows don't open underneath."
|
||||
checked: Settings.data.dock.exclusive
|
||||
onToggled: checked => Settings.data.dock.exclusive = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
NLabel {
|
||||
label: "Background Opacity"
|
||||
description: "Adjust the background opacity."
|
||||
}
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.dock.backgroundOpacity
|
||||
onMoved: value => Settings.data.dock.backgroundOpacity = value
|
||||
text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: "Dock Floating Distance"
|
||||
description: "Adjust the floating distance from the screen edge."
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 4
|
||||
stepSize: 0.01
|
||||
value: Settings.data.dock.floatingRatio
|
||||
onMoved: value => Settings.data.dock.floatingRatio = value
|
||||
text: Math.floor(Settings.data.dock.floatingRatio * 100) + "%"
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Monitor Configuration
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Monitors Configuration"
|
||||
description: "Choose which monitors should display the dock."
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: `${modelData.name || "Unknown"}${modelData.model ? `: ${modelData.model}` : ""}`
|
||||
description: `${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})`
|
||||
checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.dock.monitors = addMonitor(Settings.data.dock.monitors, modelData.name)
|
||||
} else {
|
||||
Settings.data.dock.monitors = removeMonitor(Settings.data.dock.monitors, modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,11 @@ import qs.Widgets
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
NHeader {
|
||||
label: "Profile"
|
||||
description: "Configure your user profile and avatar settings."
|
||||
}
|
||||
|
||||
// Profile section
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
@@ -48,19 +53,9 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "User Interface"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
Layout.bottomMargin: Style.marginS * scaling
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show Corners"
|
||||
description: "Display rounded corners on the edge of the screen."
|
||||
checked: Settings.data.general.showScreenCorners
|
||||
onToggled: checked => Settings.data.general.showScreenCorners = checked
|
||||
NHeader {
|
||||
label: "User Interface"
|
||||
description: "Main settings for the user interface."
|
||||
}
|
||||
|
||||
NToggle {
|
||||
@@ -79,23 +74,14 @@ ColumnLayout {
|
||||
description: "Adjust the rounded border of all UI elements."
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
NSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.general.radiusRatio
|
||||
onMoved: Settings.data.general.radiusRatio = value
|
||||
cutoutColor: Color.mSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: Math.floor(Settings.data.general.radiusRatio * 100) + "%"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: Style.marginS * scaling
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
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) + "%"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,26 +95,18 @@ ColumnLayout {
|
||||
description: "Adjust global animation speed."
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
NSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0.1
|
||||
to: 2.0
|
||||
stepSize: 0.01
|
||||
value: Settings.data.general.animationSpeed
|
||||
onMoved: Settings.data.general.animationSpeed = value
|
||||
cutoutColor: Color.mSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: Math.round(Settings.data.general.animationSpeed * 100) + "%"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: Style.marginS * scaling
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
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
|
||||
@@ -139,57 +117,42 @@ ColumnLayout {
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
NText {
|
||||
text: "Dock"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
Layout.bottomMargin: Style.marginS * scaling
|
||||
NHeader {
|
||||
label: "Screen Corners"
|
||||
description: "Customize screen corner rounding and visual effects."
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Auto-hide Dock"
|
||||
description: "Automatically hide the dock when not in use."
|
||||
checked: Settings.data.dock.autoHide
|
||||
onToggled: checked => Settings.data.dock.autoHide = checked
|
||||
label: "Show Screen Corners"
|
||||
description: "Display rounded corners on the edge of the screen."
|
||||
checked: Settings.data.general.showScreenCorners
|
||||
onToggled: checked => Settings.data.general.showScreenCorners = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Solid Black Corners"
|
||||
description: "Force screen corners to always render as solid black."
|
||||
checked: Settings.data.general.forceBlackScreenCorners
|
||||
onToggled: checked => Settings.data.general.forceBlackScreenCorners = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Dock Background Opacity"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
NLabel {
|
||||
label: "Screen Corners Radius"
|
||||
description: "Adjust the rounded corners of the screen."
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Adjust the background opacity of the dock."
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
NSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.dock.backgroundOpacity
|
||||
onMoved: Settings.data.dock.backgroundOpacity = value
|
||||
cutoutColor: Color.mSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: Style.marginS * scaling
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
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) + "%"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,12 +166,10 @@ ColumnLayout {
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
NText {
|
||||
text: "Fonts"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
Layout.bottomMargin: Style.marginS * scaling
|
||||
|
||||
NHeader {
|
||||
label: "Fonts"
|
||||
description: "Configure interface typography."
|
||||
}
|
||||
|
||||
// Font configuration section
|
||||
@@ -216,12 +177,13 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NComboBox {
|
||||
NSearchableComboBox {
|
||||
label: "Default Font"
|
||||
description: "Main font used throughout the interface."
|
||||
model: FontService.availableFonts
|
||||
currentKey: Settings.data.ui.fontDefault
|
||||
placeholder: "Select default font..."
|
||||
searchPlaceholder: "Search fonts..."
|
||||
popupHeight: 420 * scaling
|
||||
minimumWidth: 300 * scaling
|
||||
onSelected: function (key) {
|
||||
@@ -229,12 +191,13 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
NSearchableComboBox {
|
||||
label: "Fixed Width Font"
|
||||
description: "Monospace font used for terminal and code display."
|
||||
model: FontService.monospaceFonts
|
||||
currentKey: Settings.data.ui.fontFixed
|
||||
placeholder: "Select monospace font..."
|
||||
searchPlaceholder: "Search monospace fonts..."
|
||||
popupHeight: 320 * scaling
|
||||
minimumWidth: 300 * scaling
|
||||
onSelected: function (key) {
|
||||
@@ -242,12 +205,13 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
NSearchableComboBox {
|
||||
label: "Billboard Font"
|
||||
description: "Large font used for clocks and prominent displays."
|
||||
model: FontService.displayFonts
|
||||
currentKey: Settings.data.ui.fontBillboard
|
||||
placeholder: "Select display font..."
|
||||
searchPlaceholder: "Search display fonts..."
|
||||
popupHeight: 320 * scaling
|
||||
minimumWidth: 300 * scaling
|
||||
onSelected: function (key) {
|
||||
|
||||
@@ -10,6 +10,11 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
width: root.width
|
||||
|
||||
NHeader {
|
||||
label: "System Hooks"
|
||||
description: "Configure commands to be executed when system events occur."
|
||||
}
|
||||
|
||||
// Enable/Disable Toggle
|
||||
NToggle {
|
||||
label: "Enable Hooks"
|
||||
|
||||
@@ -7,104 +7,97 @@ import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NHeader {
|
||||
label: "Appearance"
|
||||
description: "Configure the launcher behavior and appearance."
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
id: launcherPosition
|
||||
label: "Position"
|
||||
description: "Choose where the Launcher panel appears."
|
||||
Layout.fillWidth: true
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "center"
|
||||
name: "Center (default)"
|
||||
}
|
||||
ListElement {
|
||||
key: "top_left"
|
||||
name: "Top Left"
|
||||
}
|
||||
ListElement {
|
||||
key: "top_right"
|
||||
name: "Top Right"
|
||||
}
|
||||
ListElement {
|
||||
key: "bottom_left"
|
||||
name: "Bottom Left"
|
||||
}
|
||||
ListElement {
|
||||
key: "bottom_right"
|
||||
name: "Bottom Right"
|
||||
}
|
||||
ListElement {
|
||||
key: "bottom_center"
|
||||
name: "Bottom Center"
|
||||
}
|
||||
ListElement {
|
||||
key: "top_center"
|
||||
name: "Top Center"
|
||||
}
|
||||
}
|
||||
currentKey: Settings.data.appLauncher.position
|
||||
onSelected: function (key) {
|
||||
Settings.data.appLauncher.position = key
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NComboBox {
|
||||
id: launcherPosition
|
||||
label: "Position"
|
||||
description: "Choose where the Launcher panel appears."
|
||||
NText {
|
||||
text: "Background Opacity"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Adjust the background opacity of the launcher."
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "center"
|
||||
name: "Center (default)"
|
||||
}
|
||||
ListElement {
|
||||
key: "top_left"
|
||||
name: "Top Left"
|
||||
}
|
||||
ListElement {
|
||||
key: "top_right"
|
||||
name: "Top Right"
|
||||
}
|
||||
ListElement {
|
||||
key: "bottom_left"
|
||||
name: "Bottom Left"
|
||||
}
|
||||
ListElement {
|
||||
key: "bottom_right"
|
||||
name: "Bottom Right"
|
||||
}
|
||||
ListElement {
|
||||
key: "bottom_center"
|
||||
name: "Bottom Center"
|
||||
}
|
||||
ListElement {
|
||||
key: "top_center"
|
||||
name: "Top Center"
|
||||
}
|
||||
}
|
||||
currentKey: Settings.data.appLauncher.position
|
||||
onSelected: function (key) {
|
||||
Settings.data.appLauncher.position = key
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
NValueSlider {
|
||||
id: launcherBgOpacity
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Background Opacity"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Adjust the background opacity of the launcher."
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
NSlider {
|
||||
id: launcherBgOpacity
|
||||
Layout.fillWidth: true
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
stepSize: 0.01
|
||||
value: Settings.data.appLauncher.backgroundOpacity
|
||||
onMoved: Settings.data.appLauncher.backgroundOpacity = value
|
||||
cutoutColor: Color.mSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
text: Math.floor(Settings.data.appLauncher.backgroundOpacity * 100) + "%"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: Style.marginS * scaling
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
}
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
stepSize: 0.01
|
||||
value: Settings.data.appLauncher.backgroundOpacity
|
||||
onMoved: value => Settings.data.appLauncher.backgroundOpacity = value
|
||||
text: Math.floor(Settings.data.appLauncher.backgroundOpacity * 100) + "%"
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Enable Clipboard History"
|
||||
description: "Show clipboard history in the launcher."
|
||||
checked: Settings.data.appLauncher.enableClipboardHistory
|
||||
onToggled: checked => Settings.data.appLauncher.enableClipboardHistory = checked
|
||||
}
|
||||
NToggle {
|
||||
label: "Enable Clipboard History"
|
||||
description: "Show clipboard history in the launcher."
|
||||
checked: Settings.data.appLauncher.enableClipboardHistory
|
||||
onToggled: checked => Settings.data.appLauncher.enableClipboardHistory = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Use App2Unit for Launching"
|
||||
description: "Use app2unit -- 'desktop-entry' when launching applications for better systemd integration."
|
||||
checked: Settings.data.appLauncher.useApp2Unit
|
||||
onToggled: checked => Settings.data.appLauncher.useApp2Unit = checked
|
||||
}
|
||||
NToggle {
|
||||
label: "Use App2Unit for Launching"
|
||||
description: "Use app2unit -- 'desktop-entry' when launching applications for better systemd integration."
|
||||
checked: Settings.data.appLauncher.useApp2Unit
|
||||
onToggled: checked => Settings.data.appLauncher.useApp2Unit = checked
|
||||
}
|
||||
|
||||
NDivider {
|
||||
|
||||
@@ -11,6 +11,11 @@ ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NHeader {
|
||||
label: "Network Settings"
|
||||
description: "Configure Wi-Fi and Bluetooth connectivity options."
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Enable Wi-Fi"
|
||||
description: "Enable Wi-Fi connectivity."
|
||||
|
||||
162
Modules/SettingsPanel/Tabs/NotificationTab.qml
Normal file
@@ -0,0 +1,162 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
// Helper functions to update arrays immutably
|
||||
function addMonitor(list, name) {
|
||||
const arr = (list || []).slice()
|
||||
if (!arr.includes(name))
|
||||
arr.push(name)
|
||||
return arr
|
||||
}
|
||||
function removeMonitor(list, name) {
|
||||
return (list || []).filter(function (n) {
|
||||
return n !== name
|
||||
})
|
||||
}
|
||||
|
||||
// General Notification Settings
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Appearance"
|
||||
description: "Configure notifications appearance and behavior."
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Do Not Disturb"
|
||||
description: "Disable all notification popups when enabled."
|
||||
checked: Settings.data.notifications.doNotDisturb
|
||||
onToggled: checked => Settings.data.notifications.doNotDisturb = checked
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Monitor Configuration
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Monitors Configuration"
|
||||
description: "Choose which monitors should display notifications."
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: `${modelData.name || "Unknown"}${modelData.model ? `: ${modelData.model}` : ""}`
|
||||
description: `${modelData.width}x${modelData.height} at (${modelData.x}, ${modelData.y})`
|
||||
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.notifications.monitors = addMonitor(Settings.data.notifications.monitors, modelData.name)
|
||||
} else {
|
||||
Settings.data.notifications.monitors = removeMonitor(Settings.data.notifications.monitors, modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Notification Duration Settings
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Notification Duration"
|
||||
description: "Configure how long notifications stay visible based on their urgency level."
|
||||
}
|
||||
|
||||
// Low Urgency Duration
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: "Low Urgency Duration"
|
||||
description: "How long low priority notifications stay visible."
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
value: Settings.data.notifications.lowUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.lowUrgencyDuration = value
|
||||
text: Settings.data.notifications.lowUrgencyDuration + "s"
|
||||
}
|
||||
}
|
||||
|
||||
// Normal Urgency Duration
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: "Normal Urgency Duration"
|
||||
description: "How long normal priority notifications stay visible."
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
value: Settings.data.notifications.normalUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.normalUrgencyDuration = value
|
||||
text: Settings.data.notifications.normalUrgencyDuration + "s"
|
||||
}
|
||||
}
|
||||
|
||||
// Critical Urgency Duration
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: "Critical Urgency Duration"
|
||||
description: "How long critical priority notifications stay visible."
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
value: Settings.data.notifications.criticalUrgencyDuration
|
||||
onMoved: value => Settings.data.notifications.criticalUrgencyDuration = value
|
||||
text: Settings.data.notifications.criticalUrgencyDuration + "s"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,15 @@ ColumnLayout {
|
||||
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NHeader {
|
||||
label: "General Settings"
|
||||
description: "Configure screen recording output and content."
|
||||
}
|
||||
|
||||
// Output Directory
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginS * scaling
|
||||
|
||||
NTextInput {
|
||||
label: "Output Directory"
|
||||
@@ -53,12 +57,8 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Video Settings"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
Layout.bottomMargin: Style.marginS * scaling
|
||||
NHeader {
|
||||
label: "Video Settings"
|
||||
}
|
||||
|
||||
// Source
|
||||
@@ -203,12 +203,8 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Audio Settings"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
Layout.bottomMargin: Style.marginS * scaling
|
||||
NHeader {
|
||||
label: "Audio Settings"
|
||||
}
|
||||
|
||||
// Audio Source
|
||||
|
||||
@@ -9,7 +9,6 @@ import qs.Widgets
|
||||
ColumnLayout {
|
||||
id: root
|
||||
width: parent.width
|
||||
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
property list<string> wallpapersList: []
|
||||
@@ -42,11 +41,9 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
// Current wallpaper display
|
||||
NText {
|
||||
text: "Current Wallpaper"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
NHeader {
|
||||
label: "Current Wallpaper"
|
||||
description: "Preview and manage your desktop background."
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -80,18 +77,9 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Wallpaper grid
|
||||
NText {
|
||||
text: "Wallpaper Selector"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Click on a wallpaper to set it as your current wallpaper."
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
NHeader {
|
||||
label: "Wallpaper Selector"
|
||||
description: "Click on a wallpaper to set it as your current wallpaper."
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,12 @@ import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NHeader {
|
||||
label: "Wallpaper Settings"
|
||||
description: "Control how wallpapers are managed and displayed."
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Enable Wallpaper Management"
|
||||
@@ -22,6 +28,7 @@ ColumnLayout {
|
||||
visible: Settings.data.wallpaper.enabled
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NTextInput {
|
||||
label: "Wallpaper Directory"
|
||||
description: "Path to your common wallpaper directory."
|
||||
@@ -61,7 +68,7 @@ ColumnLayout {
|
||||
delegate: RowLayout {
|
||||
NText {
|
||||
text: (modelData.name || "Unknown")
|
||||
color: Color.mSecondary
|
||||
color: Color.mPrimary
|
||||
font.weight: Style.fontWeightBold
|
||||
Layout.preferredWidth: 90 * scaling
|
||||
}
|
||||
@@ -89,11 +96,8 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Look & Feel"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
NHeader {
|
||||
label: "Look & Feel"
|
||||
}
|
||||
|
||||
// Fill Mode
|
||||
@@ -134,21 +138,14 @@ ColumnLayout {
|
||||
description: "Duration of transition animations in seconds."
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
NSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 500
|
||||
to: 10000
|
||||
stepSize: 100
|
||||
value: Settings.data.wallpaper.transitionDuration
|
||||
onMoved: Settings.data.wallpaper.transitionDuration = value
|
||||
cutoutColor: Color.mSurface
|
||||
}
|
||||
NText {
|
||||
text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(2) + "s"
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
}
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 500
|
||||
to: 10000
|
||||
stepSize: 100
|
||||
value: Settings.data.wallpaper.transitionDuration
|
||||
onMoved: value => Settings.data.wallpaper.transitionDuration = value
|
||||
text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(1) + "s"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,20 +156,13 @@ ColumnLayout {
|
||||
description: "Duration of transition animations in seconds."
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
NSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
value: Settings.data.wallpaper.transitionEdgeSmoothness
|
||||
onMoved: Settings.data.wallpaper.transitionEdgeSmoothness = value
|
||||
cutoutColor: Color.mSurface
|
||||
}
|
||||
NText {
|
||||
text: Math.round(Settings.data.wallpaper.transitionEdgeSmoothness * 100) + "%"
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
}
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
value: Settings.data.wallpaper.transitionEdgeSmoothness
|
||||
onMoved: value => Settings.data.wallpaper.transitionEdgeSmoothness = value
|
||||
text: Math.round(Settings.data.wallpaper.transitionEdgeSmoothness * 100) + "%"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,11 +179,8 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Automation"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
NHeader {
|
||||
label: "Automation"
|
||||
}
|
||||
|
||||
// Random Wallpaper
|
||||
|
||||
@@ -7,6 +7,12 @@ import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NHeader {
|
||||
label: "Your Location"
|
||||
description: "Set your location for weather, time zones, and scheduling."
|
||||
}
|
||||
|
||||
// Location section
|
||||
RowLayout {
|
||||
@@ -57,11 +63,9 @@ ColumnLayout {
|
||||
spacing: Style.marginM * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Weather"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mSecondary
|
||||
NHeader {
|
||||
label: "Weather"
|
||||
description: "Configure weather display preferences and temperature units."
|
||||
}
|
||||
|
||||
NToggle {
|
||||
@@ -71,4 +75,10 @@ ColumnLayout {
|
||||
onToggled: checked => Settings.data.location.useFahrenheit = checked
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,8 @@ import qs.Widgets
|
||||
NBox {
|
||||
id: root
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
Layout.fillHeight: true
|
||||
anchors.margins: Style.marginL * scaling
|
||||
|
||||
// No media player detected
|
||||
@@ -236,9 +232,7 @@ NBox {
|
||||
return 0
|
||||
return Math.max(0, Math.min(1, r))
|
||||
}
|
||||
property real effectiveRatio: (MediaService.isSeeking
|
||||
&& localSeekRatio >= 0) ? Math.max(0, Math.min(1,
|
||||
localSeekRatio)) : progressRatio
|
||||
property real effectiveRatio: (MediaService.isSeeking && localSeekRatio >= 0) ? Math.max(0, Math.min(1, localSeekRatio)) : progressRatio
|
||||
|
||||
// Debounced backend seek during drag
|
||||
Timer {
|
||||
@@ -248,8 +242,7 @@ NBox {
|
||||
onTriggered: {
|
||||
if (MediaService.isSeeking && progressWrapper.localSeekRatio >= 0) {
|
||||
const next = Math.max(0, Math.min(1, progressWrapper.localSeekRatio))
|
||||
if (progressWrapper.lastSentSeekRatio < 0 || Math.abs(
|
||||
next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) {
|
||||
if (progressWrapper.lastSentSeekRatio < 0 || Math.abs(next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) {
|
||||
MediaService.seekByRatio(next)
|
||||
progressWrapper.lastSentSeekRatio = next
|
||||
}
|
||||
@@ -265,7 +258,6 @@ NBox {
|
||||
stepSize: 0
|
||||
snapAlways: false
|
||||
enabled: MediaService.trackLength > 0 && MediaService.canSeek
|
||||
cutoutColor: Color.mSurface
|
||||
heightRatio: 0.65
|
||||
|
||||
onMoved: {
|
||||
|
||||
@@ -9,13 +9,11 @@ import qs.Widgets
|
||||
|
||||
// Power Profiles: performance, balanced, eco
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 1
|
||||
implicitHeight: powerRow.implicitHeight + Style.marginM * 2 * scaling
|
||||
|
||||
property real spacing: 0
|
||||
|
||||
// Centralized service
|
||||
readonly property bool hasPP: PowerProfileService.available
|
||||
property real spacing: 0
|
||||
|
||||
RowLayout {
|
||||
id: powerRow
|
||||
@@ -31,8 +29,7 @@ NBox {
|
||||
tooltipText: "Set performance power profile."
|
||||
enabled: hasPP
|
||||
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
||||
colorBg: (enabled
|
||||
&& PowerProfileService.profile === PowerProfile.Performance) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorBg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mOnPrimary : Color.mPrimary
|
||||
onClicked: {
|
||||
if (enabled) {
|
||||
@@ -46,8 +43,7 @@ NBox {
|
||||
tooltipText: "Set balanced power profile."
|
||||
enabled: hasPP
|
||||
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
||||
colorBg: (enabled
|
||||
&& PowerProfileService.profile === PowerProfile.Balanced) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorBg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnPrimary : Color.mPrimary
|
||||
onClicked: {
|
||||
if (enabled) {
|
||||
@@ -61,8 +57,7 @@ NBox {
|
||||
tooltipText: "Set eco power profile."
|
||||
enabled: hasPP
|
||||
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
||||
colorBg: (enabled
|
||||
&& PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorBg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mOnPrimary : Color.mPrimary
|
||||
onClicked: {
|
||||
if (enabled) {
|
||||
|
||||
@@ -16,10 +16,6 @@ NBox {
|
||||
|
||||
property string uptimeText: "--"
|
||||
|
||||
Layout.fillWidth: true
|
||||
// Height driven by content
|
||||
implicitHeight: content.implicitHeight + Style.marginM * 2 * scaling
|
||||
|
||||
RowLayout {
|
||||
id: content
|
||||
anchors.left: parent.left
|
||||
@@ -63,7 +59,7 @@ NBox {
|
||||
tooltipText: "Open settings."
|
||||
onClicked: {
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.General
|
||||
settingsPanel.open(screen)
|
||||
settingsPanel.open()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +68,7 @@ NBox {
|
||||
icon: "power"
|
||||
tooltipText: "Power menu."
|
||||
onClicked: {
|
||||
powerPanel.open(screen)
|
||||
powerPanel.open()
|
||||
sidePanel.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,6 @@ import qs.Widgets
|
||||
NBox {
|
||||
id: root
|
||||
|
||||
Layout.preferredWidth: Style.baseWidgetSize * 2.625 * scaling
|
||||
implicitHeight: content.implicitHeight + Style.marginXS * 2 * scaling
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
anchors.left: parent.left
|
||||
|
||||