Compare commits

...

193 Commits

Author SHA1 Message Date
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
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
142 changed files with 6093 additions and 4296 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

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

Binary file not shown.

View File

@@ -51,22 +51,19 @@ 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]")

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
@@ -317,6 +323,7 @@ Singleton {
property bool dimDesktop: false
property bool showScreenCorners: 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 +383,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 +399,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

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

@@ -7,7 +7,7 @@ import qs.Services
import qs.Widgets
Loader {
active: Settings.data.general.showScreenCorners
active: Settings.data.general.showScreenCorners && !Settings.data.bar.floating
sourceComponent: Variants {
model: Quickshell.screens
@@ -20,8 +20,8 @@ Loader {
screen: modelData
property color cornerColor: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
property real cornerRadius: 20 * scaling
property real cornerSize: 20 * scaling
property real cornerRadius: Style.screenRadius * scaling
property real cornerSize: Style.screenRadius * scaling
Connections {
target: ScalingService
@@ -46,12 +46,10 @@ 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
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
left: ((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: ((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,219 @@ 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)
Column {
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
}
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
// Center section (center widgets)
Column {
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
}
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
// Bottom section (right widgets)
Column {
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
}
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
}
}
// ------------------------------
// 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
}
}
}
}
}
}
// ------------------------------
// 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
}
}
}
}
// 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
}
}
}
}
}
}

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

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) + "%\nMethod: " + monitor.method + "\nLeft click for advanced settings.\nScroll up/down to change brightness."
}
onWheel: function (angle) {
@@ -104,10 +104,10 @@ Item {
}
}
onClicked: {
onRightClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Brightness
settingsPanel.open(screen)
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 useCompactMode: barPosition === "left" || barPosition === "right"
implicitWidth: useCompactMode ? Math.round(Style.capsuleHeight * scaling) : Math.round(layout.implicitWidth + Style.marginM * 2 * scaling)
implicitHeight: useCompactMode ? Math.round(Style.capsuleHeight * 2.5 * scaling) : Math.round(Style.capsuleHeight * scaling)
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: useCompactMode ? -2 * scaling : -3 * scaling
// Compact mode for vertical bars - Time section (HH, MM)
Repeater {
model: useCompactMode ? 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 (useCompactMode) {
// 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: useCompactMode ? 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: useCompactMode
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: useCompactMode ? 2 : 0
NText {
readonly property var now: Time.date
text: {
if (useCompactMode) {
// 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: !useCompactMode && (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

@@ -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) {
@@ -70,7 +69,7 @@ NIconButton {
// No script was defined, open settings
var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Bar
settingsPanel.open(screen)
settingsPanel.open()
}
}

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 for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute."
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

@@ -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,10 +29,9 @@ 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
@@ -43,8 +41,8 @@ NIconButton {
colorBorderHover: Color.transparent
anchors.verticalCenter: parent.verticalCenter
onClicked: PanelService.getPanel("sidePanel")?.toggle(screen, this)
onRightClicked: PanelService.getPanel("settingsPanel")?.toggle(screen)
onClicked: PanelService.getPanel("sidePanel")?.toggle(this)
onRightClicked: PanelService.getPanel("settingsPanel")?.toggle()
IconImage {
id: logo

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,399 @@ 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
// 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"
radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant
// CPU Usage Component
Item {
Layout.preferredWidth: cpuUsageRow.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter
visible: showCpuUsage
RowLayout {
id: mainLayout
anchors.centerIn: parent // Better centering than margins
width: parent.width - Style.marginM * scaling * 2
spacing: Style.marginS * scaling
RowLayout {
id: cpuUsageRow
anchors.centerIn: parent
spacing: Style.marginXXS * scaling
// CPU Usage Component
Item {
Layout.preferredWidth: cpuUsageRow.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter
visible: showCpuUsage
NIcon {
icon: "cpu-usage"
font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter
}
RowLayout {
id: cpuUsageRow
anchors.centerIn: parent
spacing: Style.marginXS * scaling
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
NIcon {
icon: "download-speed"
font.pointSize: Style.fontSizeS * scaling
anchors.horizontalCenter: parent.horizontalCenter
}
NText {
text: SystemStatService.formatSpeed(SystemStatService.rxSpeed)
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
}
}
}
// 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
NIcon {
icon: "upload-speed"
font.pointSize: Style.fontSizeS * scaling
anchors.horizontalCenter: parent.horizontalCenter
}
NText {
text: SystemStatService.formatSpeed(SystemStatService.txSpeed)
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
}
}
}
// 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
NIcon {
icon: "storage"
font.pointSize: Style.fontSizeS * scaling
Layout.alignment: Qt.AlignHCenter
}
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
}
}
}

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 for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute."
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

@@ -48,8 +48,7 @@ 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

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
@@ -750,7 +746,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 +797,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 +849,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

@@ -48,18 +48,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 +115,12 @@ ColumnLayout {
}
// Icon grid
ScrollView {
NScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
horizontalPolicy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AlwaysOn
GridView {
id: grid

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.alignmentRight
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: {

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. This will hide screen corners."
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
}
}

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,35 @@ 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
}
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 +159,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 +170,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 +184,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 +198,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,6 +10,11 @@ ColumnLayout {
spacing: Style.marginL * scaling
NHeader {
label: "General Settings"
description: "Configure screen recording output and content."
}
// Output Directory
ColumnLayout {
spacing: Style.marginS * scaling
@@ -53,12 +58,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 +204,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

View File

@@ -12,9 +12,6 @@ NBox {
property real spacing: 0
Layout.fillWidth: true
Layout.preferredWidth: 1
implicitHeight: utilRow.implicitHeight + Style.marginM * 2 * scaling
RowLayout {
id: utilRow
anchors.fill: parent
@@ -61,7 +58,7 @@ NBox {
onClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.WallpaperSelector
settingsPanel.open(screen)
settingsPanel.open()
}
onRightClicked: {
WallpaperService.setRandomWallpaper()

View File

@@ -11,11 +11,6 @@ NBox {
readonly property bool weatherReady: (LocationService.data.weather !== null)
// TBC weatherReady is not turning to false when we reset weather...
Layout.fillWidth: true
// Height driven by content
implicitHeight: content.implicitHeight + Style.marginL * 2 * scaling
ColumnLayout {
id: content
anchors.left: parent.left
@@ -28,8 +23,7 @@ NBox {
spacing: Style.marginS * scaling
NIcon {
Layout.alignment: Qt.AlignVCenter
icon: weatherReady ? LocationService.weatherSymbolFromCode(
LocationService.data.weather.current_weather.weathercode) : ""
icon: weatherReady ? LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode) : ""
font.pointSize: Style.fontSizeXXXL * 1.75 * scaling
color: Color.mPrimary
}
@@ -84,7 +78,7 @@ NBox {
RowLayout {
visible: weatherReady
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
spacing: Style.marginL * scaling
Repeater {
model: weatherReady ? LocationService.data.weather.daily.time : []

View File

@@ -10,32 +10,15 @@ import qs.Widgets
NPanel {
id: root
panelWidth: 460 * scaling
panelHeight: contentHeight
// Default height, will be modified via binding when the content is fully loaded
property real contentHeight: 720 * scaling
preferredWidth: 460
preferredHeight: 734
panelKeyboardFocus: true
panelContent: Item {
id: content
property real cardSpacing: Style.marginL * scaling
width: root.panelWidth
implicitHeight: layout.implicitHeight + (2 * cardSpacing)
height: implicitHeight
// Update parent's contentHeight whenever our height changes
onHeightChanged: {
root.contentHeight = height
}
onImplicitHeightChanged: {
if (implicitHeight > 0) {
root.contentHeight = implicitHeight
}
}
// Layout content
ColumnLayout {
id: layout
@@ -46,57 +29,52 @@ NPanel {
// Cards (consistent inter-card spacing via ColumnLayout spacing)
ProfileCard {
id: profileCard
Layout.fillWidth: true
Layout.preferredHeight: Math.max(64 * scaling)
}
WeatherCard {
id: weatherCard
Layout.fillWidth: true
Layout.preferredHeight: Math.max(220 * scaling)
}
// Middle section: media + stats column
RowLayout {
id: middleRow
Layout.fillWidth: true
Layout.minimumHeight: 280 * scaling
Layout.preferredHeight: Math.max(280 * scaling, statsCard.implicitHeight)
Layout.preferredHeight: Math.max(310 * scaling)
spacing: content.cardSpacing
// Media card
MediaCard {
id: mediaCard
Layout.fillWidth: true
Layout.fillHeight: true
}
// System monitors combined in one card
SystemMonitorCard {
id: statsCard
Layout.alignment: Qt.AlignTop
Layout.preferredWidth: Style.baseWidgetSize * 2.625 * scaling
Layout.fillHeight: true
}
}
// Bottom actions (two grouped rows of round buttons)
RowLayout {
id: bottomRow
Layout.fillWidth: true
Layout.minimumHeight: 60 * scaling
Layout.preferredHeight: Math.max(60 * scaling, powerProfilesCard.implicitHeight, utilitiesCard.implicitHeight)
Layout.preferredHeight: Math.max(60 * scaling)
spacing: content.cardSpacing
// Power Profiles switcher
PowerProfilesCard {
id: powerProfilesCard
spacing: content.cardSpacing
Layout.fillWidth: true
Layout.fillHeight: true
spacing: content.cardSpacing
}
// Utilities buttons
UtilitiesCard {
id: utilitiesCard
spacing: content.cardSpacing
Layout.fillWidth: true
Layout.fillHeight: true
spacing: content.cardSpacing
}
}
}

View File

@@ -0,0 +1,179 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
Rectangle {
id: root
property string message: ""
property string description: ""
property string type: "notice"
property int duration: 3000
readonly property real initialScale: 0.7
signal hidden
width: Math.min(500 * scaling, parent.width * 0.8)
height: Math.max(60 * scaling, contentLayout.implicitHeight + Style.marginL * 2 * scaling)
radius: Style.radiusL * scaling
visible: false
opacity: 0
scale: initialScale
// Clean surface background like NToast
color: Color.mSurface
// Colored border based on type
border.color: {
switch (type) {
case "warning":
return Color.mPrimary
case "error":
return Color.mError
default:
return Color.mOutline
}
}
border.width: Math.max(2, Style.borderM * scaling)
Behavior on opacity {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
Behavior on scale {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
Timer {
id: hideTimer
interval: root.duration
onTriggered: root.hide()
}
Timer {
id: hideAnimation
interval: Style.animationFast
onTriggered: {
root.visible = false
root.hidden()
}
}
RowLayout {
id: contentLayout
anchors.fill: parent
anchors.margins: Style.marginL * scaling
spacing: Style.marginL * scaling
// Icon
NIcon {
id: icon
icon: {
switch (type) {
case "warning":
return "toast-warning"
case "error":
return "toast-error"
default:
return "toast-notice"
}
}
color: {
switch (type) {
case "warning":
return Color.mPrimary
case "error":
return Color.mError
default:
return Color.mOnSurface
}
}
font.pointSize: Style.fontSizeXXL * 1.5 * scaling
Layout.alignment: Qt.AlignVCenter
}
// Label and description
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
NText {
Layout.fillWidth: true
text: root.message
color: Color.mOnSurface
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
wrapMode: Text.WordWrap
visible: text.length > 0
}
NText {
Layout.fillWidth: true
text: root.description
color: Color.mOnSurface
font.pointSize: Style.fontSizeM * scaling
wrapMode: Text.WordWrap
visible: text.length > 0
}
}
// Close button
NIconButton {
id: closeButton
icon: "close"
colorBg: Color.mSurfaceVariant
colorFg: Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.mOutline
sizeRatio: 0.8
Layout.alignment: Qt.AlignTop
onClicked: root.hide()
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: root.hide()
cursorShape: Qt.PointingHandCursor
}
function show(msg, desc, msgType, msgDuration) {
message = msg
description = desc || ""
type = msgType || "notice"
duration = msgDuration || 3000
visible = true
opacity = 1
scale = 1.0
hideTimer.restart()
}
function hide() {
hideTimer.stop()
opacity = 0
scale = initialScale
hideAnimation.start()
}
function hideImmediately() {
opacity = 0
scale = initialScale
root.visible = false
root.hidden()
}
}

View File

@@ -9,70 +9,13 @@ import qs.Widgets
Variants {
model: Quickshell.screens
delegate: Loader {
delegate: ToastScreen {
required property ShellScreen modelData
property real scaling: ScalingService.getScreenScale(modelData)
Connections {
target: ScalingService
function onScaleChanged(screenName, scale) {
if (screenName === modelData.name) {
scaling = scale
}
}
}
screen: modelData
scaling: ScalingService.getScreenScale(modelData)
// Only show on screens that have notifications enabled
active: Settings.isLoaded && modelData ? (Settings.data.notifications.monitors.includes(modelData.name)
|| (Settings.data.notifications.monitors.length === 0)) : false
sourceComponent: PanelWindow {
id: root
screen: modelData
// Position based on bar location, like Notification popup does
anchors {
top: Settings.data.bar.position === "top"
bottom: Settings.data.bar.position === "bottom"
}
// Set a width instead of anchoring left/right so we can click on the side of the toast
implicitWidth: 500 * scaling
// Small height when hidden, appropriate height when visible
implicitHeight: Math.round(toast.visible ? toast.height + Style.marginM * scaling : 1)
// Set margins based on bar position
margins.top: Settings.data.bar.position === "top" ? (Style.barHeight + Style.marginS) * scaling : 0
margins.bottom: Settings.data.bar.position === "bottom" ? (Style.barHeight + Style.marginS) * scaling : 0
// Transparent background
color: Color.transparent
// Overlay layer to appear above other panels
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: PanelWindow.ExclusionMode.Ignore
NToast {
id: toast
screen: modelData
// Simple positioning - margins already account for bar
targetY: Style.marginS * scaling
// Hidden position based on bar location
hiddenY: Settings.data.bar.position === "top" ? -toast.height - 20 : toast.height + 20
Component.onCompleted: {
// Register this toast with the service
ToastService.allToasts.push(toast)
// Connect dismissal signal
toast.dismissed.connect(ToastService.onToastDismissed)
}
}
}
// Only activate on enabled screens
active: Settings.isLoaded && modelData && (Settings.data.notifications.monitors.includes(modelData.name) || Settings.data.notifications.monitors.length === 0)
}
}

View File

@@ -0,0 +1,143 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Services
import qs.Widgets
Loader {
id: root
required property ShellScreen screen
required property real scaling
required property bool active
// Local queue for this screen only
property var messageQueue: []
property bool isShowingToast: false
// If true, immediately show new toasts
property bool replaceOnNew: true
Connections {
target: ScalingService
function onScaleChanged(screenName, scale) {
if (screenName === root.screen.name) {
root.scaling = scale
}
}
}
Connections {
target: ToastService
enabled: root.active
function onNotify(message, description, type, duration) {
root.enqueueToast({
"message": message,
"description": description,
"type": type,
"duration": duration,
"timestamp": Date.now()
})
}
}
function enqueueToast(toastData) {
if (replaceOnNew && isShowingToast) {
// Cancel current toast and clear queue for latest toast
messageQueue = [] // Clear existing queue
messageQueue.push(toastData)
// Hide current toast immediately
if (item) {
hideTimer.stop()
item.hideToast() // Need to add this method to PanelWindow
}
// Process new toast after a brief delay
isShowingToast = false
quickSwitchTimer.restart()
} else {
// Original behavior - queue the toast
messageQueue.push(toastData)
processQueue()
}
}
Timer {
id: quickSwitchTimer
interval: 50 // Brief delay for smooth transition
onTriggered: root.processQueue()
}
function processQueue() {
if (!active || !item || messageQueue.length === 0 || isShowingToast) {
return
}
var data = messageQueue.shift()
isShowingToast = true
// Show the toast
item.showToast(data.message, data.description, data.type, data.duration)
}
function onToastHidden() {
isShowingToast = false
// Small delay before next toast
hideTimer.restart()
}
Timer {
id: hideTimer
interval: 200
onTriggered: root.processQueue()
}
sourceComponent: PanelWindow {
id: panel
screen: root.screen
anchors {
top: true
}
implicitWidth: 500 * root.scaling
implicitHeight: Math.round(toastItem.visible ? toastItem.height + Style.marginM * root.scaling : 1)
// Set margins based on bar position
margins.top: {
switch (Settings.data.bar.position) {
case "top":
return (Style.barHeight + Style.marginS) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0)
default:
return Style.marginL * scaling
}
}
color: Color.transparent
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: PanelWindow.ExclusionMode.Ignore
function showToast(message, description, type, duration) {
toastItem.show(message, description, type, duration)
}
// Add method to immediately hide toast
function hideToast() {
toastItem.hideImmediately()
}
SimpleToast {
id: toastItem
anchors.horizontalCenter: parent.horizontalCenter
onHidden: root.onToastHidden()
}
}
}

View File

@@ -10,8 +10,8 @@ import qs.Widgets
NPanel {
id: root
panelWidth: 400 * scaling
panelHeight: 500 * scaling
preferredWidth: 400
preferredHeight: 500
panelKeyboardFocus: true
property string passwordSsid: ""
@@ -64,7 +64,7 @@ NPanel {
NIconButton {
icon: "close"
tooltipText: "Close"
tooltipText: "Close."
sizeRatio: 0.8
onClicked: root.close()
}
@@ -156,8 +156,7 @@ NPanel {
// Scanning state
ColumnLayout {
visible: Settings.data.network.wifiEnabled && NetworkService.scanning && Object.keys(
NetworkService.networks).length === 0
visible: Settings.data.network.wifiEnabled && NetworkService.scanning && Object.keys(NetworkService.networks).length === 0
anchors.fill: parent
spacing: Style.marginL * scaling
@@ -185,12 +184,11 @@ NPanel {
}
// Networks list container
ScrollView {
visible: Settings.data.network.wifiEnabled && (!NetworkService.scanning || Object.keys(
NetworkService.networks).length > 0)
NScrollView {
visible: Settings.data.network.wifiEnabled && (!NetworkService.scanning || Object.keys(NetworkService.networks).length > 0)
anchors.fill: parent
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AsNeeded
horizontalPolicy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AsNeeded
clip: true
ColumnLayout {
@@ -217,11 +215,9 @@ NPanel {
radius: Style.radiusM * scaling
// Add opacity for operations in progress
opacity: (NetworkService.disconnectingFrom === modelData.ssid
|| NetworkService.forgettingNetwork === modelData.ssid) ? 0.6 : 1.0
opacity: (NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid) ? 0.6 : 1.0
color: modelData.connected ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b,
0.05) : Color.mSurface
color: modelData.connected ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.05) : Color.mSurface
border.width: Math.max(1, Style.borderS * scaling)
border.color: modelData.connected ? Color.mPrimary : Color.mOutline
@@ -338,9 +334,7 @@ NPanel {
}
Rectangle {
visible: modelData.cached && !modelData.connected
&& NetworkService.forgettingNetwork !== modelData.ssid
&& NetworkService.disconnectingFrom !== modelData.ssid
visible: modelData.cached && !modelData.connected && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
color: Color.transparent
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
@@ -364,19 +358,14 @@ NPanel {
spacing: Style.marginS * scaling
NBusyIndicator {
visible: NetworkService.connectingTo === modelData.ssid
|| NetworkService.disconnectingFrom === modelData.ssid
|| NetworkService.forgettingNetwork === modelData.ssid
visible: NetworkService.connectingTo === modelData.ssid || NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid
running: visible
color: Color.mPrimary
size: Style.baseWidgetSize * 0.5 * scaling
}
NIconButton {
visible: (modelData.existing || modelData.cached) && !modelData.connected
&& NetworkService.connectingTo !== modelData.ssid
&& NetworkService.forgettingNetwork !== modelData.ssid
&& NetworkService.disconnectingFrom !== modelData.ssid
visible: (modelData.existing || modelData.cached) && !modelData.connected && NetworkService.connectingTo !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
icon: "trash"
tooltipText: "Forget network"
sizeRatio: 0.7
@@ -384,10 +373,7 @@ NPanel {
}
NButton {
visible: !modelData.connected && NetworkService.connectingTo !== modelData.ssid
&& passwordSsid !== modelData.ssid
&& NetworkService.forgettingNetwork !== modelData.ssid
&& NetworkService.disconnectingFrom !== modelData.ssid
visible: !modelData.connected && NetworkService.connectingTo !== modelData.ssid && passwordSsid !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
text: {
if (modelData.existing || modelData.cached)
return "Connect"
@@ -422,8 +408,7 @@ NPanel {
// Password input
Rectangle {
visible: passwordSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
&& NetworkService.forgettingNetwork !== modelData.ssid
visible: passwordSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
Layout.fillWidth: true
height: passwordRow.implicitHeight + Style.marginS * scaling * 2
color: Color.mSurfaceVariant
@@ -504,8 +489,7 @@ NPanel {
// Forget network
Rectangle {
visible: expandedSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
&& NetworkService.forgettingNetwork !== modelData.ssid
visible: expandedSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
Layout.fillWidth: true
height: forgetRow.implicitHeight + Style.marginS * 2 * scaling
color: Color.mSurfaceVariant
@@ -561,8 +545,7 @@ NPanel {
// Empty state when no networks
ColumnLayout {
visible: Settings.data.network.wifiEnabled && !NetworkService.scanning && Object.keys(
NetworkService.networks).length === 0
visible: Settings.data.network.wifiEnabled && !NetworkService.scanning && Object.keys(NetworkService.networks).length === 0
anchors.fill: parent
spacing: Style.marginL * scaling

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