Compare commits

...

229 Commits

Author SHA1 Message Date
Ly-sec
899595ec5c Release v2.9.2 2025-09-15 14:52:09 +02:00
Ly-sec
dbf1020636 CustomButton: add script execution/polling support with text display 2025-09-15 14:37:29 +02:00
LemmyCook
7def695c0e AboutTab: Fix hover on contributos 2025-09-15 08:36:18 -04:00
LemmyCook
b51a87a981 NSlider: slightly more discrete bg track 2025-09-15 08:36:08 -04:00
LemmyCook
3da2682111 Settings: fix about tab alignment of download button 2025-09-15 07:58:33 -04:00
Ly-sec
758f2f2e55 SystemMonitor: fix network stats, move text above storage icon (vertical bar) 2025-09-15 13:43:58 +02:00
Lysec
26a27c3393 Merge pull request #282 from Mtendekuyokwa19/everforest
Everforest
2025-09-15 09:39:42 +02:00
Mtende Kuyokwa
9d9bfb54e1 everforest light 2025-09-15 09:30:26 +02:00
Mtende Kuyokwa
ab5b1e4d82 everforest dark complete 2025-09-15 08:40:11 +02:00
Ly-sec
8cb9f04a22 ScreenCorners: add solid black option 2025-09-15 08:28:57 +02:00
Ly-sec
22bc5a3bff Move ScreenCorners to the actualy screen edges when bar is floating,
edit vesktop template
ScreenCorners: move to screen corners on floating bar
BarTab: mention the ScreenCorner changes
vesktop: make read channels have less visible text
2025-09-15 08:08:32 +02:00
Ly-sec
f5982f41a2 Remove noctalia.svg 2025-09-15 07:59:39 +02:00
Ly-sec
9a80d51b10 Vesktop: replace old theme with midnight from refact0r for better looks 2025-09-15 07:59:06 +02:00
Ly-sec
fa838ecdb1 Cleaned up ColorSchemeTab, added program checks, added firefox template
Matugen: added firefox (pywalfox) template
SidePanelToggle: use ProgramCheckerService for gpu-screen-recorder
ColorSchemeTab: use NCollapsible for matugen templates, use
ProgramCheckerService to detect available programs (for matugen
templates)
NCollapsible: create collapsible category
2025-09-15 07:44:31 +02:00
ItsLemmy
c0d6780c3d Volume/Brightness/Microphone: fixed tooltips to new mapping 2025-09-14 23:45:17 -04:00
ItsLemmy
8935f9a0f9 NPill: fix broken mouse-wheel control 2025-09-14 23:42:21 -04:00
LemmyCook
b8b97c46a0 DistroLogo: respect original colors, and avoid changing bg color when hovering to compensate. 2025-09-14 23:07:00 -04:00
Lemmy
f2bbf70f93 Merge pull request #279 from MrDowntempo/better-cats
Updated Catppuccin to use more of the official colors.
2025-09-14 22:54:44 -04:00
Corey Woodworth
ecf468f78f Updated Catppuccin to use more of the official colors. Also changed Green to Teal in Mocha and a few minor tweaks for readability. 2025-09-14 22:50:29 -04:00
Lemmy
0b35fc1d2d Merge pull request #278 from kevindiaz314/main
feat: update tokyo night light color scheme with refined color values
2025-09-14 22:47:03 -04:00
LemmyCook
94c5d73a61 NPill: fix icon bg hover color 2025-09-14 22:46:40 -04:00
LemmyCook
f399a6d9f5 TrayMenu: improve tray opening direction in vertical bar more 2025-09-14 22:44:27 -04:00
Kevin Diaz
44fd859aec feat: update tokyo night light color scheme with refined color values 2025-09-14 22:33:13 -04:00
LemmyCook
53d0c3943d TrayMenu: Fix submenu burger icon color when hovered. 2025-09-14 22:24:39 -04:00
Lemmy
d80f923802 Merge pull request #229 from matejc/main
Fix for fingerprint flow on lock screen
2025-09-14 22:18:14 -04:00
Lemmy
1b861d7b7b Merge pull request #277 from kevindiaz314/main
feat: update tokyo night color scheme with refined color values
2025-09-14 22:07:10 -04:00
LemmyCook
65933208ec NPillVertical: match NHorizontal on margins and color 2025-09-14 22:03:44 -04:00
LemmyCook
5df218a789 Bar Widgets: Removed 3 unecessary anchors 2025-09-14 21:56:55 -04:00
LemmyCook
2f7a834b55 Bar: fix centering (againg) 2025-09-14 21:55:21 -04:00
LemmyCook
eca301553e Bar: better vertical centering on horizontal bar. 2025-09-14 21:51:43 -04:00
LemmyCook
91efa38101 HorizontalPill: different color for forceOpen + better margins 2025-09-14 21:49:29 -04:00
Kevin Diaz
acfe94f736 feat: update tokyo night color scheme with refined color values 2025-09-14 21:24:23 -04:00
LemmyCook
97bfcbb9e8 Clock: height calculation similar to NPill to avoid discrepancies 2025-09-14 21:15:27 -04:00
LemmyCook
9e47d91be2 v2.9.1-dev / git 2025-09-14 21:03:21 -04:00
LemmyCook
8872002225 Release 2.9.1 2025-09-14 21:01:14 -04:00
LemmyCook
519a85b251 AudioTab: fixed spinbox 2025-09-14 20:53:42 -04:00
LemmyCook
5aa7ff7e91 NValueSlider: new component + pimped NSlider with a small gradient and removed rounded corners due to issues. 2025-09-14 20:52:32 -04:00
LemmyCook
5ce5659b38 NPills: keep hover even when force open, as there are actions available on clicks. 2025-09-14 18:21:24 -04:00
LemmyCook
00459606ce Brightness: hotfix 2025-09-14 18:19:02 -04:00
LemmyCook
da1081700a NPill: replaced "Normal" by "On Hover" 2025-09-14 18:15:07 -04:00
LemmyCook
7e965262f5 NPill: Restored the old horizontal NPill 2025-09-14 18:07:43 -04:00
LemmyCook
19312d94c3 Removing test mode on battery 2025-09-14 17:21:38 -04:00
Ly-sec
118323e6b5 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-14 23:14:59 +02:00
Ly-sec
aed81e82b0 Remove "%" from NVerticalPill add force close option to it too
NVerticalPill: add force close option
Any vertical bar widget: remove "%" display to have nice horizontal text
BarTab: add "always hide percentage" option so the pills will never
expand (opposite of always show percentage)
2025-09-14 23:13:11 +02:00
Lemmy
76c167c2c2 Merge pull request #273 from ThatOneCalculator/fix/power-menu-suspend-icon
fix(consistency): use unfilled pause icon for suspend in power menu
2025-09-14 17:08:40 -04:00
Ly-sec
852e2fa4d1 Fix N*Pill force show layout 2025-09-14 22:39:16 +02:00
Ly-sec
17dceffff6 Overview: add autoPaddingEnabled:false to MultiEffect blur 2025-09-14 22:33:44 +02:00
Kainoa Kanter
b589f37e0b use unfilled pause icon for suspend in power menu 2025-09-14 13:27:07 -07:00
Lysec
682c6af231 Merge pull request #267 from ThatOneCalculator/patch-1
docs: mention `gpu-screen-recorder` Flatpak
2025-09-14 22:25:13 +02:00
Ly-sec
d71226c6bd Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-14 22:09:40 +02:00
Ly-sec
1f3725faf8 set version to dev 2025-09-14 22:09:38 +02:00
LemmyCook
efcec1b2f9 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-14 15:58:58 -04:00
LemmyCook
651322aef0 Bar: Removed unecessary qt5compat import 2025-09-14 15:58:56 -04:00
Ly-sec
a0a3a58668 Release v2.9.0
- **Floating Mode**: Added floating option for more flexible bar positioning
- **Vertical Orientation**: New vertical bar layout support

- **Exclusive Mode**: Added exclusive setting to prevent windows from rendering behind the dock
- **Floating Distance Control**: Added control for adjusting floating distance
- **Layout Refinements**: Various layout fixes for better visual consistency

- **Enhanced Navigation**: More panels now support closing with the Escape key
- **Settings Overhaul**: Complete revamp of the settings window tab content

- **Layout Editor**: Added ability to edit keyboard layouts directly

- **GPU Temperature**: Removed GPU temperature monitoring (resolved NVIDIA compatibility issues)

- **Compact Mode**: New compact version for space-constrained layouts

- **Hyprland Stability**: Added numerous null checks for improved Hyprland compatibility
- **Niri Support**: Fixed active window detection for the Niri compositor
- **Workspace Visibility**: Added toggle to hide unoccupied workspaces

- **Monochrome Theme**: Added new monochrome color scheme option

- **Bluetooth Stability**: More stable connections and adapter state management
- **Toast Notifications**: Fixed odd toast notification behavior
- **Font Service**: Improved font service reliability and added fuzzy search for the font selection in General Tab
2025-09-14 21:51:58 +02:00
Ly-sec
b3abe44d65 Bar: remove Qt5Compat import 2025-09-14 21:37:56 +02:00
LemmyCook
5b603472bd Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-14 15:25:59 -04:00
LemmyCook
57b0fe8a21 Wi-Fi: connect and disconnect toast messages 2025-09-14 15:25:57 -04:00
Ly-sec
f5561da3cc Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-14 21:25:14 +02:00
Ly-sec
11f6475b9f Some layout fixes to toggle and slider
NSlider(withLabel): fix some small layout issues
NToggle: fix vertical centering of the thumb
2025-09-14 21:24:11 +02:00
LemmyCook
fb2c5e0470 SysMon: removed unecessary Item {} 2025-09-14 15:14:45 -04:00
LemmyCook
b1764fddc8 SysMon: larger margin 2025-09-14 15:12:08 -04:00
Ly-sec
bb7f957e44 Clock: change to mono font 2025-09-14 21:10:16 +02:00
LemmyCook
0682315c9d Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-14 15:07:03 -04:00
LemmyCook
dd100597ed SysMon: better lookin 2025-09-14 15:05:34 -04:00
Ly-sec
6bc6380ee1 FontService: even more mono font fixes 2025-09-14 21:04:46 +02:00
Ly-sec
966089e471 FontService: more mono font fixes 2025-09-14 20:47:36 +02:00
Ly-sec
3956461254 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-14 20:38:32 +02:00
Ly-sec
02d114a05e FontService: more reliable mono lookup 2025-09-14 20:36:52 +02:00
LemmyCook
3764edafa8 Widgets: improved centering 2025-09-14 14:35:15 -04:00
Ly-sec
1cd0376381 Style: reduce vertical bar to 39 2025-09-14 20:30:26 +02:00
LemmyCook
2b154e2cdb Settings: added missing end divider to tabs 2025-09-14 13:58:00 -04:00
LemmyCook
933dfc402b Wifi+BT: added right click 2025-09-14 13:48:12 -04:00
LemmyCook
34d037d7dc Toast: improved clickability around toast 2025-09-14 13:37:15 -04:00
LemmyCook
76b6626073 Trying to match all buttons left/right click.
- left click: mute/unmute, cycle between functionality
- right click: open settings
- middle click: open external settings
2025-09-14 13:35:24 -04:00
LemmyCook
d348cfc2b0 Toast: refactored service vs UI. 2025-09-14 13:29:20 -04:00
LemmyCook
f9d7de2e3c Volume: Fixed missing externalHideTimer 2025-09-14 12:32:05 -04:00
LemmyCook
2ea00fffa5 NSlider: simplification, no Halo + some rounding 2025-09-14 12:22:26 -04:00
LemmyCook
b163dab241 NCheckbox+NToggle: better look 2025-09-14 12:03:15 -04:00
LemmyCook
af0f4818d8 Autoformatting 2025-09-14 11:51:04 -04:00
LemmyCook
8b6c7632af Tray: fixed with vertical bar 2025-09-14 11:50:18 -04:00
LemmyCook
d6d51d24c9 NPill: fix NPill icon color to match or icons (mOnSurface, even tho the bg is mSurfaceVariant) 2025-09-14 11:33:48 -04:00
LemmyCook
0c6aea7154 Vertical Bar! 2025-09-14 11:26:36 -04:00
LemmyCook
a61b2edd07 Settings: fully cleanup and aligned 2025-09-14 11:23:20 -04:00
LemmyCook
c108e7707a Settings: cleanup, almost there! 2025-09-14 11:14:37 -04:00
LemmyCook
f3123ba5b1 Settings: more cleanup - wip 2025-09-14 10:57:24 -04:00
LemmyCook
c09a93af48 NHeader: use label instead of title (matches NLabel) 2025-09-14 10:24:09 -04:00
LemmyCook
2a262999ce Merge branch 'vertical-bar' of github.com:noctalia-dev/noctalia-shell into vertical-bar 2025-09-14 10:17:30 -04:00
LemmyCook
7d952dc226 Settings: new display tab 2025-09-14 10:17:28 -04:00
Ly-sec
3cb838b455 NCheckbox: edit sizing
NToggle: edit sizing, fix thumb vertical center
2025-09-14 16:01:00 +02:00
Ly-sec
7594651e05 SettingsTabs: use NHeader, move display settings around 2025-09-14 15:50:23 +02:00
Ly-sec
0d611fc891 Merge branch 'vertical-bar' of https://github.com/noctalia-dev/noctalia-shell into vertical-bar 2025-09-14 13:46:05 +02:00
Ly-sec
8982909fae Edit Style.qml so barHeight check for vertical bar
SystemMonitor.qml: edit layout a little bit
2025-09-14 13:45:17 +02:00
ItsLemmy
132b331c7c Dock: fix floating distance when bar is at the bottom 2025-09-14 07:42:10 -04:00
ItsLemmy
85cef214c8 Merge branch 'vertical-bar' of github.com:Ly-sec/Noctalia into vertical-bar 2025-09-14 07:27:16 -04:00
ItsLemmy
80b4dad199 NPill: using monospace font 2025-09-14 07:27:14 -04:00
Ly-sec
aadbc9596d NSearchableComboBox: small layout change 2025-09-14 13:25:02 +02:00
Ly-sec
0949d154c1 Merge branch 'vertical-bar' of https://github.com/noctalia-dev/noctalia-shell into vertical-bar 2025-09-14 13:22:49 +02:00
Ly-sec
a86a0d33c1 NSearchableComboBox: created, uses fuzzy find
GeneralTab: replace NComboBox with NSearchableComboBox
2025-09-14 13:22:17 +02:00
ItsLemmy
e6372a2473 VerticalBar: smaller spacing and margin 2025-09-14 07:21:49 -04:00
ItsLemmy
e3d9ab5679 NPill better naming so files stay closeby 2025-09-14 07:21:32 -04:00
Ly-sec
ccd7458ea3 KeyboardLayout: fix language detection/parsing
Bar: add a tiny bit more spacing between widgets
NHorizontalPill: fix layout
MediaMini: set size to 0 if no media is playing
2025-09-14 10:21:53 +02:00
Ly-sec
d41b59d563 KeyboardLayout: fix ukranian iso code 2025-09-14 09:31:07 +02:00
Ly-sec
290ba4ac03 Fix N*Pill expanded text layout 2025-09-14 09:26:05 +02:00
Ly-sec
1ee14df915 Make things more readable 2025-09-14 09:05:51 +02:00
LemmyCook
76376a9783 Dock: do not show dock if no app/toplevel available 2025-09-13 22:13:02 -04:00
LemmyCook
880ac93662 autoformatting 2025-09-13 22:12:44 -04:00
LemmyCook
1157c8e21d FloatingBar: Wip 2025-09-13 22:04:36 -04:00
LemmyCook
2082cfe7c7 Merge branch 'main' into vertical-bar 2025-09-13 15:27:55 -04:00
LemmyCook
9a9f2886e0 Floating Bar: Fix for #265 (overlapping panels, toasts and notifications) 2025-09-13 15:23:27 -04:00
Ly-sec
0035fbcc4e NPill: act as loder for NVerticalPill and NHorizontalPill
NHorizontalPill: should be used for anything that expands horizontal
NVerticalPill: should be used for anything that expands vertical
2025-09-13 20:52:20 +02:00
Kainoa Kanter
5ca2c2a095 docs: mention gpu-screen-recorder Flatpak 2025-09-13 11:42:41 -07:00
Lysec
46103062d0 Merge pull request #266 from povvke/fix-app2unit-steam-games
Fix steam games not launching with app2unit
2025-09-13 20:02:10 +02:00
povvke
78a41c236c use the exec string itself to launch non terminal apps 2025-09-13 20:48:34 +03:00
Ly-sec
9dfac69e9e More spacing fixes 2025-09-13 19:28:44 +02:00
LemmyCook
de72236fe5 Merge branch 'main' into vertical-bar 2025-09-13 13:06:21 -04:00
LemmyCook
101e3125a9 Vertical bar: simpler management 2025-09-13 13:06:17 -04:00
Ly-sec
b443c9f492 Add compact clock again 2025-09-13 17:46:38 +02:00
Ly-sec
2a1e7832d6 Revert 8c81514 2025-09-13 17:44:31 +02:00
Ly-sec
8c815146e6 More fixes 2025-09-13 17:34:13 +02:00
LemmyCook
acae2b8c21 Dock: border alpha follows bg opacity 2025-09-13 11:09:55 -04:00
Ly-sec
004836fc8f More layout fixes 2025-09-13 17:00:49 +02:00
Ly-sec
b51f2d16cb Change Notification location 2025-09-13 16:47:32 +02:00
Ly-sec
6fba9d9f22 NPanel positioning fixes 2025-09-13 16:45:22 +02:00
LemmyCook
335e38d461 Floating Bar: simplified settings 2025-09-13 10:16:54 -04:00
Ly-sec
ee50d84a53 Fix spacing for vertical bar 2025-09-13 15:51:21 +02:00
Ly-sec
e706dabef3 Add BarService, use signals to check state of bar and update widgets accordingly 2025-09-13 15:31:23 +02:00
Ly-sec
dcedae46e5 Horizontal bar: try to get better spacing 2025-09-13 15:21:36 +02:00
LemmyCook
f27f9d35b0 Merge branch 'hyprland-smarter-detect' 2025-09-13 09:10:50 -04:00
Ly-sec
4f5acb7114 First iteration of vertical bar 2025-09-13 14:26:20 +02:00
Lysec
25ba27cbdd Merge pull request #262 from Mtendekuyokwa19/nixpkg
invalid nixpkg change
2025-09-13 13:14:26 +02:00
Mtende Kuyokwa
74da975ed4 invalid nixpkg change 2025-09-13 13:04:54 +02:00
Ly-sec
6f6a5b364a Bar: proper top/bottom margin check 2025-09-13 10:55:14 +02:00
Ly-sec
f670f88804 NPanel: add margin if bar is floating (except for SettingsPanel) 2025-09-13 10:25:50 +02:00
Ly-sec
814cb774a6 Notification: added margin if bar is floating 2025-09-13 10:13:51 +02:00
Ly-sec
50d8b54adf Bar: add floating setting 2025-09-13 10:11:57 +02:00
LemmyCook
ae931b791f Icons: replaced most left over filled icons by the outlined counterparts.
- only kept a few filled for basic controls (carets, play, pause,
etc...)
2025-09-12 23:20:33 -04:00
LemmyCook
dd4641eedd CompositorService: improved Hyprland detection so there is no warning on Niri. 2025-09-12 22:55:13 -04:00
LemmyCook
b66bb46fc1 We don't use qmlformat, we do use qmlfmt 2025-09-12 21:18:24 -04:00
LemmyCook
5079fc78d3 Removed ArchUpdateService 2025-09-12 21:16:50 -04:00
LemmyCook
7d2eaa46e6 qmlfmt: increase line-length to 360 to avoid hard-wrap.
+ cleaned up power menu/panel
2025-09-12 21:07:11 -04:00
LemmyCook
1043eaa39f Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-12 20:45:08 -04:00
LemmyCook
f16798f6e3 LockScreen: shorter tooltips 2025-09-12 20:45:06 -04:00
Lemmy
96acb1a679 Update README.md 2025-09-12 20:42:37 -04:00
LemmyCook
0f93797ab3 LockScreen: tooltip uniformisation 2025-09-12 18:22:12 -04:00
LemmyCook
3186a84d6b Settings: using "cloud-sun" for weather tab 2025-09-12 18:18:12 -04:00
Lemmy
d3ee66d845 Merge pull request #261 from MrDowntempo/monochrome
Updated Monochrome to not be more grayscale and pretty
2025-09-12 18:05:10 -04:00
Corey Woodworth
a8837283ab Updated Monochrome to not be more grayscale and pretty 2025-09-12 16:59:08 -04:00
LemmyCook
6fe0784c00 Autoformatting 2025-09-12 16:45:35 -04:00
Lemmy
59ce164b40 Merge pull request #257 from mkuritsu/main
Add toggle to hide unoccupied workspaces
2025-09-12 16:42:36 -04:00
mkuritsu
70144eb06f Fixed redundant comparison 2025-09-12 21:33:12 +01:00
Ly-sec
5136af5d95 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-12 22:20:48 +02:00
Ly-sec
ff42244c6d Revert hardcoded font change 2025-09-12 22:20:46 +02:00
mkuritsu
3a2bb40117 Add toggle to hide unoccupied workspaces 2025-09-12 21:16:44 +01:00
LemmyCook
bcd3100849 Merge branch 'dock-better-peeking' 2025-09-12 16:10:45 -04:00
LemmyCook
c8886629ad Dock: Float improvements, can click below dock and on the side. Should fix #237 2025-09-12 16:10:03 -04:00
Ly-sec
be4a69f6e0 Replace hardcoded font with check for default fonts, fall back to
inter/roboto
Settings: use font detection function
GeneralTab: let user know that it uses default fonts and falls back to
inter/roboto
FontService: add proper checks for default fonts (sans & mono)
2025-09-12 22:00:05 +02:00
LemmyCook
62b12d5436 Bar ethernet icon: unfilled 2025-09-12 14:50:09 -04:00
LemmyCook
99e75d51b8 Bar settings icon: unfilled 2025-09-12 14:48:13 -04:00
Lemmy
079c8f0803 Merge pull request #259 from ThatOneCalculator/settings-icons
fix: consistent settings icons
2025-09-12 14:46:26 -04:00
Kainoa Kanter
16f87cbfa3 fix: consistent settings icons 2025-09-12 11:22:41 -07:00
LemmyCook
380f31fbd9 BaBar Widgets: pass a proper section name instead of a longer string. 2025-09-12 12:54:09 -04:00
LemmyCook
28677d6888 Panels: added kb focus to BTPanel, NotifHistory, SidePanel, so they close with ESC. 2025-09-12 11:29:46 -04:00
Matej Cotman
b9ae772987 feat(Modules/LockScreen): divert PAM messages to user (eg: to notify the user about fingerprint reader) 2025-09-12 18:26:15 +03:00
Matej Cotman
4265290a0f fix(fingerprint): better fingerprint integration by removing the check for empty password 2025-09-12 18:26:11 +03:00
Lemmy
07e94b0f0e Update feature_request.md 2025-09-12 10:58:27 -04:00
Lemmy
307318918d Update bug_report.md 2025-09-12 10:58:11 -04:00
Lemmy
3f6662182e Merge pull request #258 from matejc/feat/notifications-close-on-clear
feat(Modules/Notification): auto-close history panel on clear history
2025-09-12 10:52:11 -04:00
Matej Cotman
be532fa146 feat(Modules/Notification): auto-close history panel on clear history 2025-09-12 17:44:14 +03:00
mkuritsu
722a59da80 add qmlformat simple file, add .gitignore with .qmlls.ini 2025-09-12 11:48:56 +01:00
Lemmy
3c97acf00f Merge pull request #255 from MrDowntempo/monochrome
Add Monochrome (Black & White) color scheme
2025-09-12 00:03:47 -04:00
Corey Woodworth
2d4fa59c41 Add Monochrome (Black & White) color scheme 2025-09-11 23:31:52 -04:00
LemmyCook
c5ca758d3e Settings: New Dock tab. 2025-09-11 23:31:40 -04:00
LemmyCook
6f70a98b83 ColorSchemeTab: fixed currently selected scheme to match wallpaper. 2025-09-11 23:18:13 -04:00
LemmyCook
424594a11a Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-11 22:46:21 -04:00
LemmyCook
f5ac42c692 Dock: Slightly more compact 2025-09-11 22:46:19 -04:00
Lemmy
675f96d0e6 Merge pull request #254 from SeraphimRP/patch-1
Fix a missing semicolon in Nix instructions.
2025-09-11 21:48:23 -04:00
Rdr. Seraphim Pardee
9570688294 Fix a missing semicolon in Nix instructions. 2025-09-11 20:45:48 -04:00
LemmyCook
626b745ce3 Settings: Added a ScreenCorners section in the general tab. 2025-09-11 19:22:07 -04:00
Lemmy
4afb98cf4c Merge pull request #244 from juvevood/screen-corners-radius
Screen Corners use general radius ratio of settings
2025-09-11 19:17:42 -04:00
LemmyCook
df2a9a246d Dock: New "exclusive" settings to ensure no windows go below. 2025-09-11 19:14:19 -04:00
Lemmy
d80e9ba3d5 Merge pull request #252 from BinaryQuantumSoul/patch-1
Update nix readme instructions
2025-09-11 19:01:49 -04:00
Lemmy
130c68b3e2 Merge pull request #250 from matejc/fix/bluetooth-switch
fix(bluetooth): rename wifiSwitch to bluetoothSwitch
2025-09-11 18:59:33 -04:00
Lemmy
6eea4a17a4 Merge pull request #253 from SailorSnoW/fix/screen-recorder-aarch
import gpu-screen-recorder only on x86_64 in nix flake
2025-09-11 18:56:29 -04:00
SailorSnoW
40dc8633ec import gpu-screen-recorder only on x86_64 2025-09-12 00:38:38 +02:00
QuantumSoul
12ac91d125 Update README.md 2025-09-12 00:31:41 +02:00
LemmyCook
87d86911d7 BarSectionEdit: fix click in the background closing panel, fix ghost bg color when dragging 2025-09-11 18:06:12 -04:00
LemmyCook
2872a7b5c9 Using NScrollView and NListView where it matters.
Not using them in tiny ListViews (ex: NComboBox, and Media player
dropdown)
2025-09-11 17:58:28 -04:00
LemmyCook
4067896434 New components: NScrollView + NListView
Allow controlling the handle color and stuf...
2025-09-11 17:56:47 -04:00
LemmyCook
78443451e4 Bar Widgets: Hover color switched from mPrimary to mTertiary for consistency 2025-09-11 17:30:52 -04:00
LemmyCook
719f5a20e7 Bar widget editor: better colors + autoformatting 2025-09-11 16:45:33 -04:00
LemmyCook
d8b12e6d6b Rosepine: revamped light theme by following RosePine Dawn 2025-09-11 16:29:11 -04:00
LemmyCook
9a0746d737 PowerToggle: was not receiving scaling which led to a broken bar. 2025-09-11 15:56:10 -04:00
LemmyCook
77f8b3937c RosePine: improve dark theme 2025-09-11 15:13:05 -04:00
Matej Cotman
3f4313635a fix(bluetooth): rename wifiSwitch to bluetoothSwitch to fix the toggle switch 2025-09-11 21:23:22 +03:00
LemmyCook
004d92a85d SidePanelToggle: use Noctalia logo by default 2025-09-11 13:34:04 -04:00
LemmyCook
720c17258b Weather: use the regular "sun" icon (unfilled) for better uniformity 2025-09-11 11:52:33 -04:00
LemmyCook
a8b312f3a7 SidePanel: even more robust with sizing forced everywhere 2025-09-11 11:47:09 -04:00
LemmyCook
4d6361dfe5 Updated font 2025-09-11 11:46:50 -04:00
LemmyCook
1f75819795 Tabler icons: commented out all broken icons (due to Qt's font rendering) 2025-09-11 11:26:50 -04:00
LemmyCook
50ddd2916c autoformatting 2025-09-11 11:26:29 -04:00
Ly-sec
d30e14f611 CompositorService: add tons of null checks to perhaps prevent QS crashes
(and add some logging)
ActiveWindow: added debounce for icons
KeyboardLayoutService: remove console logs
2025-09-11 17:18:25 +02:00
LemmyCook
227b0dd962 removed extra logs 2025-09-11 09:46:45 -04:00
LemmyCook
ac61086c95 Autoformatting 2025-09-11 09:45:26 -04:00
LemmyCook
0980f65751 Cloud-sun icon 2025-09-11 09:45:21 -04:00
LemmyCook
7aa3da2ff4 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-11 09:45:05 -04:00
LemmyCook
83fbb8f95d Clock: factorized many settings in a single combobox 2025-09-11 09:43:52 -04:00
Ly-sec
a029463527 CompositorService: use idx for niri workspaces 2025-09-11 14:31:36 +02:00
Ly-sec
baafe54d13 Clock: small changes to compact mode 2025-09-11 13:53:45 +02:00
Ly-sec
a1cbd35202 Clock: add compact mode with nnumeric/verbose date options 2025-09-11 13:03:39 +02:00
LemmyCook
1337a35a1e Removed video 2025-09-10 22:37:28 -04:00
Lysec
61006fbed0 Update README.md 2025-09-11 04:21:55 +02:00
Ly-sec
eff4337d35 README: more updates 2025-09-11 04:18:33 +02:00
Juve
f0733f19dd add a separate configuration item for edge of screen 2025-09-11 10:11:01 +08:00
Ly-sec
818df48787 Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-11 04:01:02 +02:00
Ly-sec
0eedfba071 README: update preview 2025-09-11 04:00:49 +02:00
LemmyCook
2dc9e2f212 Bluetooth: proper synchronisation of the adapter state with the cached setting 2025-09-10 21:29:11 -04:00
LemmyCook
62a3b343cf Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-10 21:14:04 -04:00
LemmyCook
76be93a84d NPanel: fix 3 minor warnings 2025-09-10 21:14:01 -04:00
Ly-sec
b59c56170e Merge branch 'main' of https://github.com/noctalia-dev/noctalia-shell 2025-09-11 03:02:44 +02:00
Ly-sec
c9285d8c5b SystemMonitor: remove GPU temp 2025-09-11 03:02:36 +02:00
LemmyCook
b157d855a8 Merge branch 'main' of github.com:noctalia-dev/noctalia-shell 2025-09-10 20:45:56 -04:00
LemmyCook
82ac49ce85 NPanel: simplified screen/scaling management 2025-09-10 20:45:50 -04:00
Ly-sec
7247a26586 KeyboardLayout: add tons of layouts, add Commons/KeyboardLayout.qml for ease of adding new ones 2025-09-11 01:40:25 +02:00
Ly-sec
be0b568f1f KeyboardLayout: increase font size and make it all caps 2025-09-11 01:10:04 +02:00
Lysec
e4b54e518c Merge pull request #243 from juvevood/fix-powerpanel-shortcut
fix for PowerPanel Shortcut invalid
2025-09-11 00:53:50 +02:00
Lysec
6ea1e2b4c7 Update README.md 2025-09-11 00:15:52 +02:00
Ly-sec
5b4c57eae2 Set version to dev 2025-09-10 23:28:23 +02:00
Juve
4afe2d8448 Screen Corners use gerneral radius ratio of settings 2025-09-10 12:55:50 +08:00
Juve
44cdbfe5d7 fix for PowerPanel Shortcut invalid 2025-09-10 11:45:49 +08:00
150 changed files with 6907 additions and 5080 deletions

View File

@@ -1,7 +1,7 @@
---
name: Bug Report
about: Report a bug from noctalia-shell
title: "[Bug]: "
title: "[Bug] "
labels: bug
assignees: ''
---

View File

@@ -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
View File

@@ -0,0 +1 @@
.qmlls.ini

View File

@@ -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"
}
}

View 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"
}
}

View 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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

Binary file not shown.

View 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"
}

View 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}}"
}
}

View File

@@ -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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -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 {} \;

View File

@@ -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) {

View File

@@ -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")

View File

@@ -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
View 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"
}
}

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}
}
}

View File

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

View File

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

View File

@@ -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

View File

@@ -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)
}
}
}
}

View File

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

View File

@@ -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)
}

View File

@@ -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()
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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}`)

View File

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

View File

@@ -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

View File

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

View File

@@ -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"])

View File

@@ -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()
}
}

View File

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

View File

@@ -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()
}

View File

@@ -16,6 +16,5 @@ NIconButton {
sizeRatio: 0.8
colorBg: Color.mPrimary
colorFg: Color.mOnPrimary
anchors.verticalCenter: parent.verticalCenter
onClicked: ScreenRecorderService.toggleRecording()
}

View File

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

View File

@@ -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) {

View File

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

View File

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

View File

@@ -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"])

View File

@@ -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)
}

View File

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

View File

@@ -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)
}

View File

@@ -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)
}

View File

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

View File

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

View File

@@ -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()
}
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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() {

View File

@@ -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) {

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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",

View File

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

View File

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

View File

@@ -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

View File

@@ -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) : ""
}
}

View File

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

View File

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

View File

@@ -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

View File

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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

@@ -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()

View File

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

View File

@@ -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()
}
}
}
}
}

View File

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

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

View File

@@ -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) {

View File

@@ -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"

View File

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

View File

@@ -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."

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

View File

@@ -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

View File

@@ -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."
}
}

View File

@@ -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

View File

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

View File

@@ -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: {

View File

@@ -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) {

View File

@@ -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()
}
}

View File

@@ -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

Some files were not shown because too many files have changed in this diff Show More