249 Commits

Author SHA1 Message Date
end-4 45c6d8a837 Merge branch 'main' into hefty-hype 2026-05-25 00:27:46 +02:00
end-4 ad7a7fe566 Merge branch 'main' into hefty-hype 2026-05-24 23:37:13 +02:00
end-4 21689e1d51 Merge branch 'main' into hefty-hype 2026-05-14 14:37:23 +02:00
end-4 74e4e698d9 Merge branch 'main' into hefty-hype 2026-05-14 14:32:52 +02:00
end-4 20d9f80336 fix wrong merge 2026-05-14 09:34:19 +02:00
end-4 4420a6c22d Merge branch 'main' into hefty-hype 2026-05-14 09:33:48 +02:00
end-4 bac0b388ad Merge branch 'main' into hefty-hype 2026-05-13 20:37:34 +02:00
end-4 4c590874e0 fix dupe y 2026-05-13 00:07:46 +02:00
end-4 da47151345 Merge branch 'main' into hefty-hype 2026-05-12 10:47:55 +02:00
end-4 9279a5a181 Merge branch 'main' into hefty-hype 2026-05-12 09:27:19 +02:00
end-4 a9dcaf0d4b Merge branch 'main' into hefty-hype 2026-05-12 07:55:05 +02:00
end-4 a23d050df5 qs: hyprland dispatcher fixes 2026-05-12 07:24:29 +02:00
end-4 7aa9e603d5 Merge branch 'main' into hefty-hype 2026-05-12 07:23:24 +02:00
end-4 b9f668e106 Merge branch 'main' into hefty-hype 2026-05-11 23:40:19 +02:00
end-4 b9acc518e9 Merge branch 'main' into hefty-hype 2026-05-04 10:18:09 +02:00
end-4 464cddad00 Merge branch 'main' into hefty-hype 2026-04-16 22:33:02 +02:00
end-4 283995ca99 fix notif drag getting stuck 2026-04-16 22:32:54 +02:00
end-4 ef9042838e Merge branch 'main' into hefty-hype 2026-04-13 16:45:05 +02:00
end-4 87939110bf Merge branch 'main' into hefty-hype 2026-04-10 22:12:48 +02:00
end-4 a62e64e2ef Merge branch 'main' into hefty-hype 2026-04-08 10:23:53 +02:00
end-4 493fb9b686 fix more member overrides base object member warning 2026-04-08 10:19:03 +02:00
end-4 0db4f8bd5d Merge branch 'main' into hefty-hype 2026-04-08 10:18:18 +02:00
end-4 15002949ee hefty: bar: adjust hresources spacing 2026-04-07 22:58:40 +02:00
end-4 74b869c30b Merge branch 'main' into hefty-hype 2026-04-07 16:01:49 +02:00
end-4 a4d45f04f2 removed duped print 2026-04-07 15:59:59 +02:00
end-4 d87fbf5523 merge main 2026-04-05 23:20:10 +02:00
end-4 c582ecf8af merge recognize-music.sh 2026-04-05 21:55:32 +02:00
end-4 6050148835 GoogleCloud: re fetch token on expiry 2026-04-05 21:52:50 +02:00
end-4 cc582fd113 Merge branch 'main' into hefty-hype 2026-04-05 20:22:48 +02:00
end-4 c8eb9d2830 hefty: bar: fix stacked exclusivity when theres multiple monitors 2026-04-05 00:45:10 +02:00
end-4 35c272c807 Merge branch 'main' into hefty-hype 2026-04-04 23:33:46 +02:00
end-4 e552776670 add screentranslator to hefty family 2026-04-04 23:30:34 +02:00
end-4 fdcdc7f1a3 Merge branch 'main' into hefty-hype 2026-04-03 20:50:58 +02:00
end-4 566e100623 Merge branch 'main' into hefty-hype 2026-04-03 19:34:59 +02:00
end-4 2674382b5f refactor toolbar fab 2026-04-03 19:27:28 +02:00
end-4 01e2eb433b add MultiTurnProcess 2026-04-03 19:26:47 +02:00
end-4 2c30b28fb6 revealer: more correct reveal condition 2026-04-03 19:26:36 +02:00
end-4 7c27da5336 lock: refactor error shake 2026-04-03 19:26:23 +02:00
end-4 32934200e4 Merge branch 'main' into hefty-hype 2026-03-31 15:06:52 +02:00
end-4 906d7adf3e Merge branch 'main' into hefty-hype 2026-03-29 23:59:28 +02:00
end-4 37c3fface4 hefty: bar: fix weird window info popup cutoff; spacing adjustments 2026-03-29 23:52:01 +02:00
end-4 ce52695746 actually fixing popup content spacing this time 2026-03-29 19:22:18 +02:00
end-4 e597928a9e hefty: bar: workspaces: less disgusting workspace number showing logic 2026-03-29 19:21:07 +02:00
end-4 e5def85946 hefty: toplayer: fade out on locking 2026-03-29 19:20:39 +02:00
end-4 f5ae1d360f Merge branch 'main' into hefty-hype 2026-03-29 18:29:59 +02:00
end-4 7f64e5c756 hefty: bar: fix popup content spacing 2026-03-29 18:29:11 +02:00
end-4 013e81f2ac hefty: bar: adjust module spacings 2026-03-29 18:28:52 +02:00
end-4 0b2dfb1910 Merge branch 'main' into hefty-hype 2026-03-27 23:20:08 +01:00
end-4 2eb5885b56 hyprland: add windowrule for chromium screensharing popup positioning 2026-03-26 21:19:53 +01:00
end-4 beac11dc18 Merge branch 'main' into hefty-hype 2026-03-24 17:22:59 +01:00
end-4 698692a500 use tabular numbers for sidebar timers 2026-03-24 17:22:44 +01:00
end-4 ef38057830 fish: update prompt; don't be fancy in tty 2026-03-24 14:46:53 +01:00
end-4 c854d88ee9 reload kitty properly on theme change 2026-03-24 14:45:09 +01:00
end-4 e1b79693e3 hefty: bar: tray: adjust spacing & allow rmb to open unpinneds 2026-03-24 14:44:58 +01:00
end-4 30583abf36 hefty: bar: add system tray 2026-03-22 20:54:44 +01:00
end-4 12d510e9eb globalfocusgrab: try to close dismissed stuff to prevent focusgrab crash 2026-03-22 20:53:50 +01:00
end-4 61a2881ac1 hefty: bar: fix ws indocator hover when in special ws 2026-03-22 17:56:55 +01:00
end-4 96c8a63b21 hefty: fix bar covering whole screen when changing position 2026-03-22 17:56:33 +01:00
end-4 950356e47d anti-flashbang: adjust brightness curve 2026-03-22 10:09:11 +01:00
end-4 8cc6087744 region selector: breathing for region recording 2026-03-22 10:08:48 +01:00
end-4 d52fbe0b40 hefty: bar: fix some alignment issues for floating 2026-03-22 09:58:22 +01:00
end-4 193d82847a Merge branch 'main' into hefty-hype 2026-03-22 08:25:27 +01:00
end-4 725b0ef5cf hefty: bar: windowinfo: fix icon randomly not showing 2026-03-21 12:28:02 +01:00
end-4 b1b96904a9 merge from main 2026-03-21 12:05:22 +01:00
end-4 82ed7498c8 Revert "hefty: bar: fix popup bg getting clipped"
nvm that sloppy calculation made it worse
2026-03-21 11:59:41 +01:00
end-4 1e0ed08909 hefty: bar: fix popup bg getting clipped 2026-03-21 11:59:41 +01:00
end-4 350f48a74b hefty: bar: window info 2026-03-21 11:59:41 +01:00
end-4 36f01b78fe hefty: bar: make left sidebar button circular (when alone) 2026-03-21 11:59:41 +01:00
end-4 3f77086b93 change choreographergridlayout back to gridlayout 2026-03-21 11:59:41 +01:00
end-4 87f61b9331 hefty: bar: adjust battery text spacing 2026-03-21 11:59:41 +01:00
end-4 725c873ab9 hefty: bar: ws: rmb for overview 2026-03-21 11:59:41 +01:00
end-4 f02bcdcce7 hefty: bar: resources: dont keep settings open 2026-03-21 11:57:48 +01:00
end-4 6b7a12fc9f hefty: more subtle focus grab darkening 2026-03-21 11:57:48 +01:00
end-4 6878368d71 music recognition script fix from main 2026-03-21 11:57:48 +01:00
end-4 467f19b40e hefty: bar: resources: make battery bolt icon not weird 2026-03-21 11:57:21 +01:00
end-4 90c3b642b2 hefty: bar: make popups not move while open 2026-03-21 11:57:21 +01:00
end-4 6e433e4a39 hefty: bar: resources indicator: add more stats 2026-03-21 11:57:21 +01:00
end-4 05eb696ead ii: ActiveWindow: remove unused props 2026-03-21 11:56:33 +01:00
end-4 0818a6ad9c wicons: wifi: also check for connected status 2026-03-21 11:56:33 +01:00
end-4 8ca3ef773a network: fix strength prop 2026-03-21 11:55:58 +01:00
end-4 e2aa71b59e hefty: bar: add "left sidebar" button
for now...
i'll probably replace the left sidebar later
2026-03-21 11:55:24 +01:00
end-4 e4ad01c20f hefty: bar: resources popup: show mem & cpu 2026-03-21 11:55:24 +01:00
end-4 522bb5dc0a ii bar resources popup: use formatting func from ResourceUsage 2026-03-21 11:55:24 +01:00
end-4 80752b7812 hefty: bar: add wifi bt sound indicator 2026-03-21 11:55:24 +01:00
end-4 247da26c7b make battery icons adapt to battery percentage 2026-03-21 11:55:24 +01:00
end-4 09d92aff7b hefty: bar: battery: checkmark when full, more natural order for vertical 2026-03-21 11:55:24 +01:00
end-4 3bb9127e32 replace 9999 radius with pill 2026-03-21 11:55:24 +01:00
end-4 a316f91e86 tiny reload popup design tweaks 2026-03-21 11:55:24 +01:00
end-4 b4c8a63d1a move material symbol in Network service to Icon 2026-03-21 11:55:24 +01:00
end-4 68a51b79ff directories: add user ai prompts dir creation 2026-03-21 11:55:24 +01:00
end-4 8d1b9a2ea0 hefty: bar: add power button widget 2026-03-21 11:55:24 +01:00
end-4 ace263cd45 hefty: bar: fix widget backgrounds flash when changing size 2026-03-21 11:55:24 +01:00
end-4 134bf4d986 rename HSystemInfo to HResources 2026-03-21 11:55:24 +01:00
end-4 481e848a65 hefty: bar: fix weird popup shape when smaller than container 2026-03-21 11:55:24 +01:00
end-4 17c2702d2b boxes: add spacing prop 2026-03-21 11:55:24 +01:00
end-4 84d7928518 refactor flexible grids into Box 2026-03-21 11:55:24 +01:00
end-4 a81f52fcd7 hefty: bar: make battery vertical-compatible 2026-03-21 11:55:24 +01:00
end-4 6eda3c674d hefty: use ii overview for now; add global focus scrim 2026-03-21 11:54:47 +01:00
end-4 54f4709d5f hefty: bar: put popups in loaders 2026-03-21 11:54:47 +01:00
end-4 7a46a5c4e5 faster text anim 2026-03-21 11:54:47 +01:00
end-4 25dba608ae hefty: bar: systeminfo: add to default bar config, add ppd btns 2026-03-21 11:54:47 +01:00
end-4 6eb547bb18 make focus rings not go behind 2026-03-21 11:54:47 +01:00
end-4 cd1a0b3d69 set default calendar locale to C instead of bri'ish 2026-03-21 11:54:47 +01:00
end-4 842b09c548 hefty: bar: adjust calendar spacing 2026-03-21 11:54:47 +01:00
end-4 f7998dd7c4 add focus ring to group and ripple buttons 2026-03-21 11:54:47 +01:00
end-4 43177b9cf3 hefty: bar: fix popup displaying wrong value 2026-03-21 11:54:47 +01:00
end-4 ef86c64933 hefty: bar: make day of week row reliable 2026-03-21 11:54:47 +01:00
end-4 bd767f140f hefty: bar: add system info indicator (only battery for now) 2026-03-21 11:54:47 +01:00
end-4 7215d465ea ClippedProgressBar: switch from opacitymask to multieffect 2026-03-21 11:54:47 +01:00
end-4 67a19bedaf battery service: add energy rate known-or-not status 2026-03-21 11:54:47 +01:00
end-4 e3dbaf4242 MaterialSymbol: allow customization of fill anim 2026-03-21 11:54:47 +01:00
end-4 1fe219e215 hefty: bar: fix wrong placement of center-right group 2026-03-21 11:54:47 +01:00
end-4 e012eb646c refactor fixed width text 2026-03-21 11:54:47 +01:00
end-4 485372ee56 hefty: bar: fix ws icons sometimes not showing 2026-03-21 11:54:47 +01:00
end-4 b14e0c494c refactor time duration formatting 2026-03-21 11:54:47 +01:00
end-4 1ad259ff9b change spring anims to expressive default spatial 2026-03-21 11:54:47 +01:00
end-4 2109cf5e09 notifications: dont print on discard 2026-03-21 11:54:47 +01:00
end-4 a33122cd3c hefty: bar: time: calendar 2026-03-21 11:54:47 +01:00
end-4 7a4468258b hefty: bar: make popups work for vertical 2026-03-21 11:54:47 +01:00
end-4 ae0c9f4731 hefty: bar: popup choreography 2026-03-21 11:54:47 +01:00
end-4 521f3cab6d hefty: bar: refactor content hover effect 2026-03-21 11:54:47 +01:00
end-4 455252dff1 hefty: bar: click to close popups 2026-03-21 11:54:47 +01:00
end-4 60ba555de6 refactor bar widget shape background and make it work at the right edge 2026-03-21 11:54:47 +01:00
end-4 485c40406d changes from main 2026-03-21 11:54:47 +01:00
end-4 98a766608d hefty: bar: time: popout 2026-03-21 11:54:47 +01:00
end-4 026a660aa7 hefty: bar: ws: nicer ws icon anim on populate 2026-03-21 11:54:47 +01:00
end-4 9e76e89b94 hefty: bar: ws: proper occupied ws detection for numbers/dots 2026-03-21 11:54:47 +01:00
end-4 da5b8e8912 hefty: bar: cleaner hover anim when in special ws 2026-03-21 11:54:47 +01:00
end-4 bdf91e1d16 hefty: bar: center time 2026-03-21 11:54:47 +01:00
end-4 139e64c28e hefty: bar: ws: consistent icons & bouncy icon dodge anim 2026-03-21 11:54:47 +01:00
end-4 2d94bace7b thicker text weight 2026-03-21 11:54:47 +01:00
end-4 e7368bdc25 hefty: bar: ws: improve hover indicator readability 2026-03-21 11:54:47 +01:00
end-4 09cdad1554 hefty: bar: time widget 2026-03-21 11:54:47 +01:00
end-4 8b84537579 hefty: bar: ws: bouncy special ws 2026-03-21 11:54:47 +01:00
end-4 ea35ca1582 hefty: bar: proper vertical layouting 2026-03-21 11:54:47 +01:00
end-4 8fccfef9f1 hefty: bar: ws: make number map work 2026-03-21 11:54:47 +01:00
end-4 9e6c1d7c08 revert weird launcher entries with terminal apps 2026-03-21 11:54:47 +01:00
end-4 aaff9d5273 hefty: bar: special ws indication 2026-03-21 11:54:47 +01:00
end-4 6f633122ed hefty: bar: workspace widget 2026-03-21 11:54:47 +01:00
end-4 24392e3791 hefty: bar: allow vertical 2026-03-21 11:52:41 +01:00
end-4 0e049db304 bar: component loader 2026-03-21 11:52:41 +01:00
end-4 680d8e85c8 changes from main n stuff 2026-03-21 11:52:41 +01:00
end-4 6ac1861e1e hefty: make overview bg work with bottom position 2026-03-21 11:50:59 +01:00
end-4 7f59246c6d make bar work with bottom position 2026-03-21 11:50:59 +01:00
end-4 f3e26d8f22 hefty: morphing thingy 2026-03-21 11:50:59 +01:00
end-4 0a185efcc5 Revert "hefty: bar: fix popup bg getting clipped"
nvm that sloppy calculation made it worse
2026-03-20 23:49:33 +01:00
end-4 124ea7b245 hefty: bar: fix popup bg getting clipped 2026-03-20 23:42:48 +01:00
end-4 4fcc12444f hefty: bar: window info 2026-03-20 23:38:14 +01:00
end-4 aefc88755a hefty: bar: make left sidebar button circular (when alone) 2026-03-20 23:36:26 +01:00
end-4 216a44274e change choreographergridlayout back to gridlayout 2026-03-20 23:35:51 +01:00
end-4 db7caae4b5 hefty: bar: adjust battery text spacing 2026-03-19 22:58:38 +01:00
end-4 c4f9b20b23 hefty: bar: ws: rmb for overview 2026-03-19 22:56:25 +01:00
end-4 17a99ec068 update quickshell 2026-03-19 16:46:30 +01:00
end-4 1225c014e8 add anti-flashbang using shader 2026-03-19 12:34:32 +01:00
end-4 5ac40d5445 refactor gamemodetoggle's config option fetching 2026-03-19 12:31:30 +01:00
end-4 08b7b393cb changes from main 2026-03-19 12:30:07 +01:00
end-4 0c69a344d5 sessionwarnings: pacman: only check critical operations 2026-03-19 08:47:41 +01:00
end-4 3ca94d1e70 make game mode persistent 2026-03-18 08:23:44 +01:00
end-4 c538505f94 hefty: bar: resources: dont keep settings open 2026-03-15 17:31:28 +01:00
end-4 3cac4206f9 hefty: more subtle focus grab darkening 2026-03-15 17:30:56 +01:00
end-4 b3d15cbae1 music recognition script fix from main 2026-03-15 17:01:07 +01:00
end-4 495e205934 hefty: bar: resources: make battery bolt icon not weird 2026-03-15 17:00:49 +01:00
end-4 ad922ac368 hefty: bar: make popups not move while open 2026-03-15 17:00:25 +01:00
end-4 6393092e63 hefty: bar: resources indicator: add more stats 2026-03-15 13:17:40 +01:00
end-4 e0f2875141 changes from main 2026-03-14 22:04:57 +01:00
end-4 83dcb349da ii: ActiveWindow: remove unused props 2026-03-14 22:02:20 +01:00
end-4 675e14e338 wicons: wifi: also check for connected status 2026-03-14 22:01:59 +01:00
end-4 79fe2651cc ai: extra user models fix from main 2026-03-14 21:57:04 +01:00
end-4 da7bddf1d1 network: fix strength prop 2026-03-14 21:56:40 +01:00
end-4 a66d0f3146 matugen 4.0 fix 2026-03-13 14:31:25 +01:00
end-4 cb5966de0f hefty: bar: add "left sidebar" button
for now...
i'll probably replace the left sidebar later
2026-03-12 13:11:04 +01:00
end-4 d0e51ffe19 add fluent ethernet filled icon 2026-03-12 13:10:05 +01:00
end-4 32e8f90b14 hefty: bar: resources popup: show mem & cpu 2026-03-12 11:23:48 +01:00
end-4 b59c3f5c30 ii bar resources popup: use formatting func from ResourceUsage 2026-03-12 11:22:26 +01:00
end-4 f9bd67699c hefty: bar: add wifi bt sound indicator 2026-03-12 11:21:58 +01:00
end-4 e3eeff8d5d make battery icons adapt to battery percentage 2026-03-11 23:33:45 +01:00
end-4 0b0693b1fd hefty: bar: battery: checkmark when full, more natural order for vertical 2026-03-11 22:11:33 +01:00
end-4 227d822655 replace 9999 radius with pill 2026-03-11 22:10:23 +01:00
end-4 5e16f2bc10 tiny reload popup design tweaks 2026-03-11 22:09:54 +01:00
end-4 be87a1ae79 move material symbol in Network service to Icon 2026-03-11 21:55:56 +01:00
end-4 653dc9c95f directories: add user ai prompts dir creation 2026-03-10 08:22:46 +01:00
end-4 e2805ef1a1 hefty: bar: add power button widget 2026-03-10 08:13:31 +01:00
end-4 6111956c4a hefty: bar: fix widget backgrounds flash when changing size 2026-03-10 08:13:21 +01:00
end-4 ff1dfedc72 rename HSystemInfo to HResources 2026-03-09 23:28:14 +01:00
end-4 4e162bd8a6 hefty: bar: fix weird popup shape when smaller than container 2026-03-09 23:27:52 +01:00
end-4 6bd6f30a5e boxes: add spacing prop 2026-03-09 23:27:11 +01:00
end-4 6952d89a7f refactor flexible grids into Box 2026-03-09 11:03:56 +01:00
end-4 d1dc89b9f2 hefty: bar: make battery vertical-compatible 2026-03-08 21:51:54 +01:00
end-4 1113b6162c fix sidebar detach crash (from main) 2026-03-08 21:50:31 +01:00
end-4 d41cda858c hefty: use ii overview for now; add global focus scrim 2026-03-08 21:02:47 +01:00
end-4 38dbc8769b hefty: bar: put popups in loaders 2026-03-08 21:02:16 +01:00
end-4 7a9b080616 faster text anim 2026-03-08 21:01:21 +01:00
end-4 a174ed1a84 hefty: bar: systeminfo: add to default bar config, add ppd btns 2026-03-08 19:58:37 +01:00
end-4 245aae965f make focus rings not go behind 2026-03-08 19:57:57 +01:00
end-4 3d5c43135a set default calendar locale to C instead of bri'ish 2026-03-08 19:31:35 +01:00
end-4 96b8af05cc hefty: bar: adjust calendar spacing 2026-03-08 19:19:28 +01:00
end-4 422db1ee91 add focus ring to group and ripple buttons 2026-03-08 19:19:10 +01:00
end-4 8e21d8c8e3 hefty: bar: fix popup displaying wrong value 2026-03-08 18:45:35 +01:00
end-4 3b13d0c938 hefty: bar: make day of week row reliable 2026-03-08 18:41:41 +01:00
end-4 f8840e8389 hefty: bar: add system info indicator (only battery for now) 2026-03-08 18:17:44 +01:00
end-4 3a4804653d ClippedProgressBar: switch from opacitymask to multieffect 2026-03-08 18:17:01 +01:00
end-4 dd0b8a5114 battery service: add energy rate known-or-not status 2026-03-08 18:16:31 +01:00
end-4 6b08e16222 MaterialSymbol: allow customization of fill anim 2026-03-08 18:15:50 +01:00
end-4 233a06a4c0 hefty: bar: fix wrong placement of center-right group 2026-03-08 18:15:25 +01:00
end-4 2a714378c9 refactor fixed width text 2026-03-08 18:15:00 +01:00
end-4 e480477db2 hefty: bar: fix ws icons sometimes not showing 2026-03-08 18:14:38 +01:00
end-4 83baff7894 refactor time duration formatting 2026-03-08 18:09:34 +01:00
end-4 3da23ce176 change spring anims to expressive default spatial 2026-03-07 21:49:38 +01:00
end-4 635d49cad0 notifications: dont print on discard 2026-03-07 21:47:12 +01:00
end-4 9ffe4dfb11 hefty: bar: time: calendar 2026-03-07 21:47:00 +01:00
end-4 c155bde816 changes from main 2026-03-05 16:21:30 +01:00
end-4 6cd2d31c99 hefty: bar: make popups work for vertical 2026-03-05 16:20:06 +01:00
end-4 7a74897b47 update quickshell 2026-03-03 10:35:24 +01:00
end-4 28936dd226 hefty: bar: popup choreography 2026-03-02 21:55:18 +01:00
end-4 aa4c18b86a waffles: more readable months when scrolling 2026-03-02 21:54:06 +01:00
end-4 0235e56af3 update hyprland config 2026-02-27 22:50:19 +01:00
end-4 6b29e73ab7 hefty: bar: refactor content hover effect 2026-02-22 00:12:26 +01:00
end-4 2bb001a62b hefty: bar: click to close popups 2026-02-21 23:10:57 +01:00
end-4 15afa07b14 gtk theme changes from main 2026-02-21 21:56:41 +01:00
end-4 bb10002976 refactor bar widget shape background and make it work at the right edge 2026-02-20 22:58:52 +01:00
end-4 368df5b717 changes from main 2026-02-20 22:56:59 +01:00
end-4 6f5ab232a6 hefty: bar: time: popout 2026-02-15 17:40:23 +01:00
end-4 3447198e13 Update shapes 2026-02-15 17:39:48 +01:00
end-4 1c295ddaac accent color preventing wall switch fix from main 2026-02-11 20:42:03 +01:00
end-4 7ba30cee4a hefty: bar: ws: nicer ws icon anim on populate 2026-02-11 20:41:33 +01:00
end-4 6fd2bd51ad hefty: bar: ws: proper occupied ws detection for numbers/dots 2026-02-08 23:54:04 +01:00
end-4 aec849c820 hefty: bar: cleaner hover anim when in special ws 2026-02-08 20:31:14 +01:00
end-4 be4eb16b60 clearer session screen download warning, update check config option 2026-02-08 20:29:49 +01:00
end-4 314b0ab3d0 hefty: bar: center time 2026-02-07 10:00:35 +01:00
end-4 10491f2362 kb focus fix from main 2026-02-07 09:46:31 +01:00
end-4 c2f65e79bc hefty: bar: ws: consistent icons & bouncy icon dodge anim 2026-02-07 09:46:21 +01:00
end-4 6bb0c251a9 thicker text weight 2026-02-07 09:45:54 +01:00
end-4 e1913d0e95 hefty: bar: ws: improve hover indicator readability 2026-02-06 23:52:31 +01:00
end-4 74c012c930 hefty: bar: time widget 2026-02-06 23:38:05 +01:00
end-4 74368ad25a hefty: bar: ws: bouncy special ws 2026-02-06 22:38:58 +01:00
end-4 4718922c55 hefty: bar: proper vertical layouting 2026-02-06 22:38:45 +01:00
end-4 d8eb55fea9 fish: more typo aliases 2026-02-06 21:49:53 +01:00
end-4 5f9fe2d250 hefty: bar: ws: make number map work 2026-02-06 21:38:00 +01:00
end-4 4a44994168 revert weird launcher entries with terminal apps 2026-02-06 21:37:27 +01:00
end-4 879111fe01 ai: remove openrouter deepseek 2026-02-06 21:10:36 +01:00
end-4 ed8c8ae8d7 hefty: bar: special ws indication 2026-02-06 20:49:03 +01:00
end-4 ead98b98b8 hefty: bar: workspace widget 2026-02-05 12:49:36 +01:00
end-4 5bb1aa06af hefty: bar: allow vertical 2026-02-03 22:43:09 +01:00
end-4 58184f5be8 bar: component loader 2026-02-03 18:17:02 +01:00
end-4 28e580c2b1 changes from main n stuff 2026-02-03 14:47:08 +01:00
end-4 819fa81fc6 make lock push hack work for multimonitor 2026-01-10 10:42:53 +01:00
end-4 fb13cc1a21 hefty: make overview bg work with bottom position 2026-01-05 23:23:10 +01:00
end-4 d1988bef02 make bar work with bottom position 2026-01-05 20:27:39 +01:00
end-4 7872fba6fe hefty: morphing thingy 2026-01-04 22:08:53 +01:00
121 changed files with 4972 additions and 513 deletions
+2 -2
View File
@@ -16,8 +16,8 @@ listener {
listener { listener {
timeout = 600 # 10mins timeout = 600 # 10mins
on-timeout = hyprctl dispatch 'hl.dsp.dpms({ action = "disable" })' on-timeout = hyprctl dispatch 'hl.dsp.dpms(false)'
on-resume = hyprctl dispatch 'hl.dsp.dpms({ action = "enable" })' on-resume = hyprctl dispatch 'hl.dsp.dpms(true)'
} }
listener { listener {
+6 -6
View File
@@ -202,12 +202,12 @@ for i = 1, 10 do
end, { description = "Window: Send to workspace " .. i }) end, { description = "Window: Send to workspace " .. i })
end end
--# We also use raw keycodes because some keyboard layouts register number keys as different chars. The codes can be verified with `wev` --# We also use raw keycodes because some keyboard layouts register number keys as different chars. The codes can be verified with `wev`
-- for i = 1, 10 do for i = 1, 10 do
-- local numberkey = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 } local numberkey = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }
-- hl.bind("SUPER + ALT + code:" .. numberkey[i], function() hl.bind("SUPER + ALT + code:" .. numberkey[i], function()
-- hl.dispatch(hl.dsp.window.move({ workspace = workspace_in_group(i), follow = false })) hl.dispatch(hl.dsp.window.move({ workspace = workspace_in_group(i), follow = false }))
-- end) end)
-- end end
--# keypad numbers --# keypad numbers
for i = 1, 10 do for i = 1, 10 do
local numpadkey = { 87, 88, 89, 83, 84, 85, 79, 80, 81, 90 } local numpadkey = { 87, 88, 89, 83, 84, 85, 79, 80, 81, 90 }
+152 -140
View File
@@ -1,160 +1,172 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
Scope { Scope {
id: root id: root
property bool failed; property bool failed
property string errorString; property string errorString
property real progressHeight: 3
// Connect to the Quickshell global to listen for the reload signals. // Connect to the Quickshell global to listen for the reload signals.
Connections { Connections {
target: Quickshell target: Quickshell
function onReloadCompleted() { function onReloadCompleted() {
root.failed = false; root.failed = false;
popupLoader.loading = true; popupLoader.loading = true;
} }
function onReloadFailed(error: string) { function onReloadFailed(error: string) {
// Close any existing popup before making a new one. // Close any existing popup before making a new one.
popupLoader.active = false; popupLoader.active = false;
root.failed = true; root.failed = true;
root.errorString = error; root.errorString = error;
popupLoader.loading = true; popupLoader.loading = true;
} }
} }
// Keep the popup in a loader because it isn't needed most of the time // Keep the popup in a loader because it isn't needed most of the time
LazyLoader { LazyLoader {
id: popupLoader id: popupLoader
PanelWindow { PanelWindow {
id: popup id: popup
exclusiveZone: 0 exclusiveZone: 0
anchors.top: true anchors.top: true
margins.top: 0 margins.top: 0
implicitWidth: rect.width + shadow.radius * 2 implicitWidth: rect.width + 8 * 2
implicitHeight: rect.height + shadow.radius * 2 implicitHeight: rect.height + 8 * 2
WlrLayershell.namespace: "quickshell:reloadPopup" WlrLayershell.namespace: "quickshell:reloadPopup"
// color blending is a bit odd as detailed in the type reference. // color blending is a bit odd as detailed in the type reference.
color: "transparent" color: "transparent"
Rectangle { RectangularShadow {
id: rect
anchors.centerIn: parent
color: failed ? "#ffe99195" : "#ffD1E8D5"
implicitHeight: layout.implicitHeight + 30
implicitWidth: layout.implicitWidth + 30
radius: 12
// Fills the whole area of the rectangle, making any clicks go to it,
// which dismiss the popup.
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: {
popupLoader.active = false
}
// makes the mouse area track mouse hovering, so the hide animation
// can be paused when hovering.
hoverEnabled: true
}
ColumnLayout {
id: layout
spacing: 10
anchors {
top: parent.top
topMargin: 10
horizontalCenter: parent.horizontalCenter
}
Text {
renderType: Text.NativeRendering
font.family: "Google Sans Flex"
font.pointSize: 14
text: root.failed ? "Quickshell: Reload failed" : "Quickshell reloaded"
color: failed ? "#ff93000A" : "#ff0C1F13"
}
Text {
renderType: Text.NativeRendering
font.family: "JetBrains Mono NF"
font.pointSize: 11
text: root.errorString
color: failed ? "#ff93000A" : "#ff0C1F13"
// When visible is false, it also takes up no space.
visible: root.errorString != ""
}
}
// A progress bar on the bottom of the screen, showing how long until the
// popup is removed.
Rectangle {
z: 2
id: bar
color: failed ? "#ff93000A" : "#ff0C1F13"
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: 10
height: 5
radius: 9999
PropertyAnimation {
id: anim
target: bar
property: "width"
from: rect.width - bar.anchors.margins * 2
to: 0
duration: failed ? 10000 : 1000
onFinished: popupLoader.active = false
// Pause the animation when the mouse is hovering over the popup,
// so it stays onscreen while reading. This updates reactively
// when the mouse moves on and off the popup.
paused: mouseArea.containsMouse
}
}
// Its bg
Rectangle {
z: 1
id: bar_bg
color: failed ? "#30af1b25" : "#4027643e"
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: 10
height: 5
radius: 9999
width: rect.width - bar.anchors.margins * 2
}
// We could set `running: true` inside the animation, but the width of the
// rectangle might not be calculated yet, due to the layout.
// In the `Component.onCompleted` event handler, all of the component's
// properties and children have been initialized.
Component.onCompleted: anim.start()
}
DropShadow {
id: shadow
anchors.fill: rect anchors.fill: rect
horizontalOffset: 0 radius: rect.radius
verticalOffset: 2 blur: 6.3
radius: 6 offset: Qt.vector2d(0.0, 1.0)
samples: radius * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs spread: 1
color: "#44000000" color: "#55000000"
source: rect
} }
}
} Rectangle {
id: rect
anchors.centerIn: parent
color: root.failed ? "#ffe99195" : "#ffD1E8D5"
implicitHeight: layout.implicitHeight + 30
implicitWidth: layout.implicitWidth + 30
radius: 8
// Fills the whole area of the rectangle, making any clicks go to it,
// which dismiss the popup.
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: {
popupLoader.active = false;
}
// makes the mouse area track mouse hovering, so the hide animation
// can be paused when hovering.
hoverEnabled: true
}
ColumnLayout {
id: layout
spacing: 10
anchors {
top: parent.top
topMargin: 10
horizontalCenter: parent.horizontalCenter
}
Text {
id: title
renderType: Text.NativeRendering
font.family: "Google Sans Flex"
font.pointSize: 14
text: root.failed ? "Quickshell: Reload failed" : "Quickshell reloaded"
color: root.failed ? "#ff93000A" : "#ff0C1F13"
}
Text {
id: info
renderType: Text.NativeRendering
font.family: "JetBrains Mono NF"
font.pointSize: 11
text: root.errorString
color: root.failed ? "#ff93000A" : "#ff0C1F13"
// When visible is false, it also takes up no space.
visible: root.errorString != ""
}
}
// A progress bar on the bottom of the screen, showing how long until the
// popup is removed.
Rectangle {
id: bar
z: 2
color: root.failed ? "#ff93000A" : "#ff0C1F13"
property real maxWidth: Math.max(title.width, info.width)
anchors {
left: parent.left
leftMargin: (parent.width - maxWidth) / 2
bottom: parent.bottom
bottomMargin: 10
}
height: root.progressHeight
radius: 9999
PropertyAnimation {
id: anim
target: bar
property: "width"
from: Math.max(title.width, info.width)
to: 0
duration: root.failed ? 10000 : 1000
onFinished: popupLoader.active = false
// Pause the animation when the mouse is hovering over the popup,
// so it stays onscreen while reading. This updates reactively
// when the mouse moves on and off the popup.
paused: mouseArea.containsMouse
}
}
// Its bg
Rectangle {
id: bar_bg
z: 1
color: root.failed ? "#30af1b25" : "#4027643e"
property real maxWidth: Math.max(title.width, info.width)
anchors {
left: parent.left
right: parent.right
leftMargin: (parent.width - maxWidth) / 2
rightMargin: anchors.leftMargin
bottom: parent.bottom
bottomMargin: 10
}
height: root.progressHeight
radius: 9999
width: bar.width
}
// We could set `running: true` inside the animation, but the width of the
// rectangle might not be calculated yet, due to the layout.
// In the `Component.onCompleted` event handler, all of the component's
// properties and children have been initialized.
Component.onCompleted: anim.start()
}
}
}
} }
@@ -224,7 +224,7 @@ Singleton {
} }
property QtObject variableAxes: QtObject { property QtObject variableAxes: QtObject {
property var main: ({ property var main: ({
"wght": 450, "wght": 500,
"wdth": 100, "wdth": 100,
}) })
property var numbers: ({ property var numbers: ({
@@ -3,7 +3,9 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import qs.modules.common.functions
import "functions"
import "config"
Singleton { Singleton {
id: root id: root
@@ -13,6 +15,8 @@ Singleton {
property int readWriteDelay: 50 // milliseconds property int readWriteDelay: 50 // milliseconds
property bool blockWrites: false property bool blockWrites: false
signal reloaded()
function setNestedValue(nestedKey, value) { function setNestedValue(nestedKey, value) {
let keys = nestedKey.split("."); let keys = nestedKey.split(".");
let obj = root.options; let obj = root.options;
@@ -48,7 +52,7 @@ Singleton {
interval: root.readWriteDelay interval: root.readWriteDelay
repeat: false repeat: false
onTriggered: { onTriggered: {
configFileView.reload() configFileView.reload();
} }
} }
@@ -57,7 +61,7 @@ Singleton {
interval: root.readWriteDelay interval: root.readWriteDelay
repeat: false repeat: false
onTriggered: { onTriggered: {
configFileView.writeAdapter() configFileView.writeAdapter();
} }
} }
@@ -68,7 +72,10 @@ Singleton {
blockWrites: root.blockWrites blockWrites: root.blockWrites
onFileChanged: fileReloadTimer.restart() onFileChanged: fileReloadTimer.restart()
onAdapterUpdated: fileWriteTimer.restart() onAdapterUpdated: fileWriteTimer.restart()
onLoaded: root.ready = true onLoaded: {
if (!root.ready) root.reloaded()
root.ready = true
}
onLoadFailed: error => { onLoadFailed: error => {
if (error == FileViewError.FileNotFound) { if (error == FileViewError.FileNotFound) {
writeAdapter(); writeAdapter();
@@ -152,8 +159,8 @@ Singleton {
property JsonObject apps: JsonObject { property JsonObject apps: JsonObject {
property string bluetooth: "kcmshell6 kcm_bluetooth" property string bluetooth: "kcmshell6 kcm_bluetooth"
property string changePassword: "kitty -1 --hold=yes fish -i -c 'passwd'" property string changePassword: "kitty -1 --hold=yes fish -i -c 'passwd'"
property string network: "kcmshell6 kcm_networkmanagement"
property string manageUser: "kcmshell6 kcm_users" property string manageUser: "kcmshell6 kcm_users"
property string network: "kcmshell6 kcm_networkmanagement"
property string networkEthernet: "kcmshell6 kcm_networkmanagement" property string networkEthernet: "kcmshell6 kcm_networkmanagement"
property string taskManager: "plasma-systemmonitor --page-name Processes" property string taskManager: "plasma-systemmonitor --page-name Processes"
property string terminal: "kitty -1" // This is only for shell actions property string terminal: "kitty -1" // This is only for shell actions
@@ -238,9 +245,15 @@ Singleton {
property bool floatStyleShadow: true // Show shadow behind bar when cornerStyle == 1 (Float) property bool floatStyleShadow: true // Show shadow behind bar when cornerStyle == 1 (Float)
property bool borderless: false // true for no grouping of items property bool borderless: false // true for no grouping of items
property string topLeftIcon: "spark" // Options: "distro" or any icon name in ~/.config/quickshell/ii/assets/icons property string topLeftIcon: "spark" // Options: "distro" or any icon name in ~/.config/quickshell/ii/assets/icons
property list<string> screenList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command
property bool showBackground: true property bool showBackground: true
property bool verbose: true property bool verbose: true
property bool vertical: false property bool vertical: false
property JsonObject indicators: JsonObject {
property JsonObject notifications: JsonObject {
property bool showUnreadCount: false
}
}
property JsonObject resources: JsonObject { property JsonObject resources: JsonObject {
property bool alwaysShowSwap: true property bool alwaysShowSwap: true
property bool alwaysShowCpu: true property bool alwaysShowCpu: true
@@ -248,7 +261,9 @@ Singleton {
property int swapWarningThreshold: 85 property int swapWarningThreshold: 85
property int cpuWarningThreshold: 90 property int cpuWarningThreshold: 90
} }
property list<string> screenList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command property JsonObject tooltips: JsonObject {
property bool clickToShow: false
}
property JsonObject utilButtons: JsonObject { property JsonObject utilButtons: JsonObject {
property bool showScreenSnip: true property bool showScreenSnip: true
property bool showColorPicker: false property bool showColorPicker: false
@@ -258,6 +273,13 @@ Singleton {
property bool showPerformanceProfileToggle: false property bool showPerformanceProfileToggle: false
property bool showScreenRecord: false property bool showScreenRecord: false
} }
property JsonObject weather: JsonObject {
property bool enable: false
property bool enableGPS: true // gps based location
property string city: "" // When 'enableGPS' is false
property bool useUSCS: false // Instead of metric (SI) units
property int fetchInterval: 10 // minutes
}
property JsonObject workspaces: JsonObject { property JsonObject workspaces: JsonObject {
property bool monochromeIcons: true property bool monochromeIcons: true
property int shown: 10 property int shown: 10
@@ -267,21 +289,6 @@ Singleton {
property list<string> numberMap: ["1", "2"] // Characters to show instead of numbers on workspace indicator property list<string> numberMap: ["1", "2"] // Characters to show instead of numbers on workspace indicator
property bool useNerdFont: false property bool useNerdFont: false
} }
property JsonObject weather: JsonObject {
property bool enable: false
property bool enableGPS: true // gps based location
property string city: "" // When 'enableGPS' is false
property bool useUSCS: false // Instead of metric (SI) units
property int fetchInterval: 10 // minutes
}
property JsonObject indicators: JsonObject {
property JsonObject notifications: JsonObject {
property bool showUnreadCount: false
}
}
property JsonObject tooltips: JsonObject {
property bool clickToShow: false
}
} }
property JsonObject battery: JsonObject { property JsonObject battery: JsonObject {
@@ -293,7 +300,10 @@ Singleton {
} }
property JsonObject calendar: JsonObject { property JsonObject calendar: JsonObject {
property string locale: "en-GB" property string locale: "C"
property bool force2CharDayOfWeek: true
property bool animate: false // Disabled by default cuz laggy
property bool weekScrollPrecision: false // One scroll advances 1 week instead of 1 month
} }
property JsonObject cheatsheet: JsonObject { property JsonObject cheatsheet: JsonObject {
@@ -341,7 +351,8 @@ Singleton {
property int mouseScrollFactor: 120 property int mouseScrollFactor: 120
property int touchpadScrollFactor: 450 property int touchpadScrollFactor: 450
} }
property JsonObject deadPixelWorkaround: JsonObject { // Hyprland leaves out 1 pixel on the right for interactions property JsonObject deadPixelWorkaround: JsonObject {
// Hyprland leaves out 1 pixel on the right for interactions
property bool enable: false property bool enable: false
} }
} }
@@ -356,7 +367,7 @@ Singleton {
} }
property JsonObject launcher: JsonObject { property JsonObject launcher: JsonObject {
property list<string> pinnedApps: [ "org.kde.dolphin", "kitty", "cmake-gui"] property list<string> pinnedApps: ["org.kde.dolphin", "kitty", "cmake-gui"]
} }
property JsonObject light: JsonObject { property JsonObject light: JsonObject {
@@ -399,7 +410,7 @@ Singleton {
property JsonObject notifications: JsonObject { property JsonObject notifications: JsonObject {
property int timeout: 7000 property int timeout: 7000
property JsonObject monitor: JsonObject { property JsonObject forceMonitor: JsonObject {
property bool enable: false property bool enable: false
property string name: "" // Name of the monitor to show notifications on, like "eDP-1". Find out with 'hyprctl monitors' command property string name: "" // Name of the monitor to show notifications on, like "eDP-1". Find out with 'hyprctl monitors' command
} }
@@ -465,7 +476,7 @@ Singleton {
property bool monochromeIcons: true property bool monochromeIcons: true
property bool showItemId: false property bool showItemId: false
property bool invertPinnedItems: true // Makes the below a whitelist for the tray and blacklist for the pinned area property bool invertPinnedItems: true // Makes the below a whitelist for the tray and blacklist for the pinned area
property list<var> pinnedItems: [ "Fcitx" ] property list<var> pinnedItems: ["Fcitx"]
property bool filterPassive: true property bool filterPassive: true
} }
@@ -529,12 +540,30 @@ Singleton {
property JsonObject android: JsonObject { property JsonObject android: JsonObject {
property int columns: 5 property int columns: 5
property list<var> toggles: [ property list<var> toggles: [
{ "size": 2, "type": "network" }, {
{ "size": 2, "type": "bluetooth" }, "size": 2,
{ "size": 1, "type": "idleInhibitor" }, "type": "network"
{ "size": 1, "type": "mic" }, },
{ "size": 2, "type": "audio" }, {
{ "size": 2, "type": "nightLight" } "size": 2,
"type": "bluetooth"
},
{
"size": 1,
"type": "idleInhibitor"
},
{
"size": 1,
"type": "mic"
},
{
"size": 2,
"type": "audio"
},
{
"size": 2,
"type": "nightLight"
}
] ]
} }
} }
@@ -548,7 +577,7 @@ Singleton {
} }
property JsonObject screenRecord: JsonObject { property JsonObject screenRecord: JsonObject {
property string savePath: Directories.videos.replace("file://","") // strip "file://" property string savePath: Directories.videos.replace("file://", "") // strip "file://"
} }
property JsonObject screenSnip: JsonObject { property JsonObject screenSnip: JsonObject {
@@ -582,11 +611,11 @@ Singleton {
property int adviseUpdateThreshold: 75 // packages property int adviseUpdateThreshold: 75 // packages
property int stronglyAdviseUpdateThreshold: 200 // packages property int stronglyAdviseUpdateThreshold: 200 // packages
} }
property JsonObject wallpaperSelector: JsonObject { property JsonObject wallpaperSelector: JsonObject {
property bool useSystemFileDialog: false property bool useSystemFileDialog: false
} }
property JsonObject windows: JsonObject { property JsonObject windows: JsonObject {
property bool showTitlebar: true // Client-side decoration for shell apps property bool showTitlebar: true // Client-side decoration for shell apps
property bool centerTitle: true property bool centerTitle: true
@@ -608,26 +637,8 @@ Singleton {
} }
} }
property JsonObject waffles: JsonObject { property JsonObject hefty: HeftyConfig {}
// Some spots are kinda janky/awkward. Setting the following to property JsonObject waffles: WaffleConfig {}
// false will make (some) stuff also be like that for accuracy.
// Example: the right-click menu of the Start button
property JsonObject tweaks: JsonObject {
property bool switchHandlePositionFix: true
property bool smootherMenuAnimations: true
property bool smootherSearchBar: true
}
property JsonObject bar: JsonObject {
property bool bottom: true
property bool leftAlignApps: false
}
property JsonObject actionCenter: JsonObject {
property list<string> toggles: [ "network", "bluetooth", "easyEffects", "powerProfile", "idleInhibitor", "nightLight", "darkMode", "antiFlashbang", "cloudflareWarp", "mic", "musicRecognition", "notifications", "onScreenKeyboard", "gameMode", "screenSnip", "colorPicker" ]
}
property JsonObject calendar: JsonObject {
property bool force2CharDayOfWeek: true
}
}
} }
} }
} }
@@ -20,7 +20,8 @@ Singleton {
readonly property string music: StandardPaths.standardLocations(StandardPaths.MusicLocation)[0] readonly property string music: StandardPaths.standardLocations(StandardPaths.MusicLocation)[0]
readonly property string videos: StandardPaths.standardLocations(StandardPaths.MoviesLocation)[0] readonly property string videos: StandardPaths.standardLocations(StandardPaths.MoviesLocation)[0]
// Other dirs used by the shell, without "file://" /////// Stuff below are without "file://" /////////
// General
property string assetsPath: Quickshell.shellPath("assets") property string assetsPath: Quickshell.shellPath("assets")
property string scriptPath: Quickshell.shellPath("scripts") property string scriptPath: Quickshell.shellPath("scripts")
property string favicons: FileUtils.trimFileProtocol(`${Directories.cache}/media/favicons`) property string favicons: FileUtils.trimFileProtocol(`${Directories.cache}/media/favicons`)
@@ -30,9 +31,6 @@ Singleton {
property string booruDownloads: FileUtils.trimFileProtocol(Directories.pictures + "/homework") property string booruDownloads: FileUtils.trimFileProtocol(Directories.pictures + "/homework")
property string booruDownloadsNsfw: FileUtils.trimFileProtocol(Directories.pictures + "/homework/🌶️") property string booruDownloadsNsfw: FileUtils.trimFileProtocol(Directories.pictures + "/homework/🌶️")
property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/media/latex`) property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/media/latex`)
property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse`)
property string shellConfigName: "config.json"
property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}`
property string todoPath: FileUtils.trimFileProtocol(`${Directories.state}/user/todo.json`) property string todoPath: FileUtils.trimFileProtocol(`${Directories.state}/user/todo.json`)
property string notesPath: FileUtils.trimFileProtocol(`${Directories.state}/user/notes.txt`) property string notesPath: FileUtils.trimFileProtocol(`${Directories.state}/user/notes.txt`)
property string conflictCachePath: FileUtils.trimFileProtocol(`${Directories.cache}/conflict-killer`) property string conflictCachePath: FileUtils.trimFileProtocol(`${Directories.cache}/conflict-killer`)
@@ -43,24 +41,39 @@ Singleton {
property string screenshotTemp: "/tmp/quickshell/media/screenshot" property string screenshotTemp: "/tmp/quickshell/media/screenshot"
property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`) property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`)
property string defaultAiPrompts: Quickshell.shellPath("defaults/ai/prompts") property string defaultAiPrompts: Quickshell.shellPath("defaults/ai/prompts")
property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/ai/prompts`)
property string userActions: FileUtils.trimFileProtocol(`${Directories.shellConfig}/actions`)
property string aiChats: FileUtils.trimFileProtocol(`${Directories.state}/user/ai/chats`) property string aiChats: FileUtils.trimFileProtocol(`${Directories.state}/user/ai/chats`)
property string aiTranslationScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/ai/gemini-translate.sh`) property string aiTranslationScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/ai/gemini-translate.sh`)
property string recordScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/videos/record.sh`) property string recordScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/videos/record.sh`)
property string userAvatarPathAccountsService: FileUtils.trimFileProtocol(`/var/lib/AccountsService/icons/${SystemInfo.username}`) property string userAvatarPathAccountsService: FileUtils.trimFileProtocol(`/var/lib/AccountsService/icons/${SystemInfo.username}`)
property string userAvatarPathRicersAndWeirdSystems: FileUtils.trimFileProtocol(`${Directories.home}.face`) property string userAvatarPathRicersAndWeirdSystems: FileUtils.trimFileProtocol(`${Directories.home}.face`)
property string userAvatarPathRicersAndWeirdSystems2: FileUtils.trimFileProtocol(`${Directories.home}.face.icon`) property string userAvatarPathRicersAndWeirdSystems2: FileUtils.trimFileProtocol(`${Directories.home}.face.icon`)
// User
property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse`)
property string shellConfigName: "config.json"
property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}`
property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/ai/prompts`)
property string userActions: FileUtils.trimFileProtocol(`${Directories.shellConfig}/actions`)
property string userComponents: FileUtils.trimFileProtocol(`${Directories.shellConfig}/components`)
// Cleanup on init // Cleanup on init
Component.onCompleted: { Component.onCompleted: {
Quickshell.execDetached(["mkdir", "-p", `${shellConfig}`])
Quickshell.execDetached(["mkdir", "-p", `${favicons}`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${coverArt}'; mkdir -p '${coverArt}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${latexOutput}'; mkdir -p '${latexOutput}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${cliphistDecode}'; mkdir -p '${cliphistDecode}'`])
Quickshell.execDetached(["mkdir", "-p", `${aiChats}`]) Quickshell.execDetached(["mkdir", "-p", `${aiChats}`])
Quickshell.execDetached(["mkdir", "-p", `${userActions}`]) Quickshell.execDetached(["mkdir", "-p", `${favicons}`])
Quickshell.execDetached(["mkdir", "-p", `${shellConfig}`])
Quickshell.execDetached(["rm", "-rf", `${tempImages}`]) Quickshell.execDetached(["rm", "-rf", `${tempImages}`])
Quickshell.execDetached(["mkdir", "-p", `${userComponents}`])
Quickshell.execDetached(["mkdir", "-p", `${userAiPrompts}`])
Quickshell.execDetached(["mkdir", "-p", `${userActions}`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${cliphistDecode}'; mkdir -p '${cliphistDecode}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${coverArt}'; mkdir -p '${coverArt}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${latexOutput}'; mkdir -p '${latexOutput}'`])
}
Component.onDestruction: {
Quickshell.execDetached(["bash", "-c", `rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${cliphistDecode}'; mkdir -p '${cliphistDecode}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${coverArt}'; mkdir -p '${coverArt}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${latexOutput}'; mkdir -p '${latexOutput}'`])
} }
} }
@@ -0,0 +1,21 @@
import QtQuick
import Quickshell
import Quickshell.Io
JsonObject {
property JsonObject bar: JsonObject {
property list<var> leftWidgets: ["HLeftSidebarButton", "HWindowInfo"]
property list<var> centerLeftWidgets: ["HTime"]
property list<var> centerWidgets: ["HWorkspaces"]
property list<var> centerRightWidgets: ["HResources"]
property list<var> rightWidgets: ["HSystemTray", "HSystemIndicators"]
property bool m3ExpressiveGrouping: true
property JsonObject resources: JsonObject {
property bool showMemory: false
property bool showRam: false
property bool showSwap: false
property bool showCpu: false
}
}
}
@@ -0,0 +1,21 @@
import QtQuick
import Quickshell
import Quickshell.Io
JsonObject {
// Some spots are kinda janky/awkward. Setting the following to
// false will make (some) stuff also be like that for accuracy.
// Example: the right-click menu of the Start button
property JsonObject tweaks: JsonObject {
property bool switchHandlePositionFix: true
property bool smootherMenuAnimations: true
property bool smootherSearchBar: true
}
property JsonObject bar: JsonObject {
property bool bottom: true
property bool leftAlignApps: false
}
property JsonObject actionCenter: JsonObject {
property list<string> toggles: [ "network", "bluetooth", "easyEffects", "powerProfile", "idleInhibitor", "nightLight", "darkMode", "antiFlashbang", "cloudflareWarp", "mic", "musicRecognition", "notifications", "onScreenKeyboard", "gameMode", "screenSnip", "colorPicker" ]
}
}
@@ -109,7 +109,8 @@ Singleton {
*/ */
function transparentize(color, percentage = 1) { function transparentize(color, percentage = 1) {
var c = Qt.color(color); var c = Qt.color(color);
return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage)); var a = c.a * (1 - clamp01(percentage));
return Qt.rgba(c.r, c.g, c.b, a);
} }
/** /**
@@ -121,7 +122,7 @@ Singleton {
*/ */
function applyAlpha(color, alpha) { function applyAlpha(color, alpha) {
var c = Qt.color(color); var c = Qt.color(color);
var a = Math.max(0, Math.min(1, alpha)); var a = clamp01(alpha);
return Qt.rgba(c.r, c.g, c.b, a); return Qt.rgba(c.r, c.g, c.b, a);
} }
@@ -24,4 +24,19 @@ Singleton {
targetDate.setDate(firstDayDate.getDate() + i); targetDate.setDate(firstDayDate.getDate() + i);
return targetDate; return targetDate;
} }
function formatDuration(seconds) {
const d = Math.floor(seconds / 86400);
const h = Math.floor((seconds % 86400) / 3600);
const m = Math.floor((seconds % 3600) / 60);
let str = "";
if (d > 0)
str += `${d}d`;
if (h > 0)
str += `${str ? ", " : ""}${h}h`;
if (m > 0 || !str)
str += `${str ? ", " : ""}${m}m`;
return str;
}
} }
@@ -0,0 +1,16 @@
pragma Singleton
import Quickshell
Singleton {
id: root
/**
* Rounds the given number to the nearest even integer.
*
* @param {number} num - The number to round.
* @returns {number} The nearest even integer.
*/
function roundToEven(num) {
return Math.round(num / 2) * 2;
}
}
@@ -95,4 +95,15 @@ Singleton {
} }
} }
} }
function findParentWithProperty(obj, propertyName) {
let current = obj;
while (current) {
if (current.hasOwnProperty(propertyName)) {
return current;
}
current = current.parent;
}
return null;
}
} }
@@ -4,7 +4,7 @@ import QtQuick
// The former animates faster than the latter, see the NumberAnimations below // The former animates faster than the latter, see the NumberAnimations below
QtObject { QtObject {
id: root id: root
required property int index property int index
property real idx1: index property real idx1: index
property real idx2: index property real idx2: index
@@ -0,0 +1,63 @@
import QtQuick
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.services
import qs.modules.common as C
NestableObject {
id: root
required property HyprlandMonitor monitor
readonly property var liveMonitorData: HyprlandData.monitors.find(m => m.id === monitor.id)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
readonly property int activeWorkspace: monitor?.activeWorkspace?.id
readonly property bool currentWorkspaceNotFake: activeWindow?.activated ?? false // Active empty workspace = fake. At least, that's how I like to call it.
readonly property int fakeWorkspace: currentWorkspaceNotFake ? -9999 : activeWorkspace
readonly property int shownCount: C.Config.options.bar.workspaces.shown
readonly property int group: Math.floor((activeWorkspace - 1) / shownCount)
readonly property var specialWorkspace: liveMonitorData?.specialWorkspace
readonly property string specialWorkspaceName: specialWorkspace.name.replace("special:", "")
readonly property bool specialWorkspaceActive: specialWorkspaceName !== ""
property list<bool> occupied: []
property list<var> biggestWindow: occupied.map((_, index) => {
const wsId = getWorkspaceIdAt(index);
var biggestWindow = HyprlandData.biggestWindowForWorkspace(wsId);
return biggestWindow;
})
function getWorkspaceId(group, index) {
return group * root.shownCount + index + 1;
}
function getWorkspaceIdAt(index) {
return root.getWorkspaceId(root.group, index);
}
// Function to update workspaceOccupied
function updateWorkspaceOccupied() {
root.occupied = Array.from({
length: root.shownCount
}, (_, i) => {
const thisWorkspaceId = getWorkspaceId(root.group, i);
return Hyprland.workspaces.values.some(ws => ws.id === thisWorkspaceId);
});
}
// Occupied workspace updates
Component.onCompleted: updateWorkspaceOccupied()
Connections {
target: Hyprland.workspaces
function onValuesChanged() {
root.updateWorkspaceOccupied();
}
}
Connections {
target: Hyprland
function onFocusedWorkspaceChanged() {
root.updateWorkspaceOccupied();
}
}
onGroupChanged: {
updateWorkspaceOccupied();
}
}
@@ -8,7 +8,7 @@ QuickToggleModel {
name: Translation.tr("Internet") name: Translation.tr("Internet")
statusText: Network.networkName statusText: Network.networkName
tooltipText: Translation.tr("%1 | Right-click to configure").arg(Network.networkName) tooltipText: Translation.tr("%1 | Right-click to configure").arg(Network.networkName)
icon: Network.materialSymbol icon: Icons.getNetworkMaterialSymbol()
toggled: Network.wifiStatus !== "disabled" toggled: Network.wifiStatus !== "disabled"
mainAction: () => Network.toggleWifi() mainAction: () => Network.toggleWifi()
@@ -0,0 +1,19 @@
pragma ComponentBehavior: Bound
import QtQuick
import ".."
Item {
id: root
property real progress: 0
default property Item child
implicitWidth: child.implicitWidth
implicitHeight: child.implicitHeight
children: [child]
property var animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
Behavior on progress {
animation: root.animation
}
}
@@ -0,0 +1,62 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
Control {
id: root
property list<real> valueWeights: [1]
property list<real> values: [0.5]
property list<color> valueHighlights: ["white"]
property list<color> valueTroughs: []
readonly property list<real> normalizedValueWeights: {
const totalWeight = valueWeights.reduce((sum, weight) => sum + weight, 0)
return valueWeights.map(weight => weight / totalWeight)
}
readonly property list<real> visualEnds: {
let cumsum = 0;
let positions = [];
for (let i = 0; i < normalizedValueWeights.length; i++) {
cumsum += normalizedValueWeights[i];
positions.push(cumsum);
}
return positions;
}
readonly property list<real> visualPositions: {
let positions = [];
let lastEnd = 0;
for(let i = 0; i < visualEnds.length; i++) {
const thisEnd = visualEnds[i];
const width = thisEnd - lastEnd;
const thisPos = lastEnd + width * values[i];
positions.push(thisPos);
lastEnd = visualEnds[i];
}
return positions;
}
readonly property list<var> visualSegments: {
let segs = [];
let lastEnd = 0;
for(let i = 0; i < visualEnds.length; i++) {
const thisEnd = visualEnds[i];
const thisPos = visualPositions[i];
segs.push([lastEnd, thisPos]);
segs.push([thisPos, thisEnd]);
lastEnd = visualEnds[i];
}
return segs;
}
readonly property list<color> segmentColors: {
var cols = [];
for(let i = 0; i < valueHighlights.length; i++) {
cols.push(valueHighlights[i]);
cols.push(valueTroughs[i]);
}
return cols;
}
}
@@ -0,0 +1,15 @@
import QtQuick
import org.kde.kirigami as Kirigami
import qs.services
import qs.modules.common
Kirigami.Icon {
id: root
property real implicitSize: 26
implicitWidth: implicitSize
implicitHeight: implicitSize
roundToIconSize: false
animated: true // It's just fading from one icon to another
}
@@ -0,0 +1,13 @@
pragma ComponentBehavior: Bound
import QtQuick
StyledRectangle {
property bool vertical: false
property real startRadius
property real endRadius
topLeftRadius: startRadius
topRightRadius: vertical ? startRadius : endRadius
bottomLeftRadius: vertical ? endRadius : startRadius
bottomRightRadius: endRadius
}
@@ -0,0 +1,12 @@
import QtQuick
RectangularContainerShape {
property bool vertical: false
property real startRadius
property real endRadius
topLeftRadius: startRadius
topRightRadius: vertical ? startRadius : endRadius
bottomLeftRadius: vertical ? endRadius : startRadius
bottomRightRadius: endRadius
}
@@ -0,0 +1,16 @@
pragma ComponentBehavior: Bound
import QtQuick
// A type that's both capable of being rows and columns
// Qt Row is just a locked down Grid smh
// Calling it a Box because that's how row-or-column widget is called in Gtk
Grid {
id: root
property bool vertical: false
columns: vertical ? 1 : -1
rows: vertical ? -1 : 1
property alias spacing: root.rowSpacing
columnSpacing: rowSpacing
}
@@ -0,0 +1,18 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
// Box, Layout version
// A type that's both capable of being rows and columns
// Qt Row is just a locked down Grid smh
// Calling it a Box because that's how row-or-column widget is called in Gtk
GridLayout {
id: root
property bool vertical: false
columns: vertical ? 1 : -1
rows: vertical ? -1 : 1
property alias spacing: root.rowSpacing
columnSpacing: rowSpacing
}
@@ -0,0 +1,9 @@
pragma ComponentBehavior: Bound
import QtQuick
// MouseArea that contains good defaults for buttons
MouseArea {
id: root
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
}
@@ -0,0 +1,36 @@
pragma ComponentBehavior: Bound
import QtQuick
/**
* Replacement for QtQuick Controls DayOfWeek row.
* I have to do this because that one is somehow really unreliable in my dynamically loaded widget
*/
Row {
id: root
property Component delegate
property alias model: repeater.model
property var locale: Qt.locale()
readonly property var firstDayOfWeek: locale.firstDayOfWeek
Repeater {
id: repeater
model: Array.from({
length: 7
}, (_, i) => {
const day = (root.firstDayOfWeek + i + 7 - 1) % 7 + 1
return ({
// Convert Locale day of week enum values to that of Qt enum values for
// consistency with DayOfWeekRow. Note that Locale day of week enum values are 0-indexed,
// while Qt day of week enum values are 1-indexed.
// Refererences:
// Locale enum values: https://doc.qt.io/qt-6/qml-qtqml-locale.html#firstDayOfWeek-prop
// DayOfWeek model values: https://doc.qt.io/qt-6/qml-qtquick-controls-dayofweekrow.html#delegate-prop
// which mentions the enum values in the Qt namespace at: https://doc.qt.io/qt-6/qt.html#DayOfWeek-enum
day: day,
shortName: root.locale.toString(new Date(2024, 0, day), "ddd")
})
})
delegate: root.delegate
}
}
@@ -1,15 +1,10 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQml import QtQml
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
import qs
import qs.services
import qs.modules.common import qs.modules.common
import qs.modules.common.widgets import qs.modules.common.widgets
import qs.modules.common.functions import qs.modules.common.functions
import qs.modules.waffle.looks
Item { Item {
id: root id: root
@@ -36,15 +31,23 @@ Item {
const diffWeeks = Math.round(diffMillis / root.millisPerWeek); const diffWeeks = Math.round(diffMillis / root.millisPerWeek);
root.targetWeekDiff += diffWeeks; root.targetWeekDiff += diffWeeks;
} }
function scrollToToday() {
root.targetWeekDiff = 0;
}
property int weeksPerScroll: 1 property int weeksPerScroll: 1
property real targetWeekDiff: 0 property real targetWeekDiff: 0
property real weekDiff: targetWeekDiff property real weekDiff: targetWeekDiff
property int contentWeekDiff: weekDiff // whole part of weekDiff property int contentWeekDiff: weekDiff // whole part of weekDiff
property bool scrolling: false property bool scrolling: false
property Animation scrollAnimation: NumberAnimation {
duration: Config.options.calendar.animate ? Appearance.animation.scroll.duration : 0
easing.type: Appearance.animation.scroll.type
easing.bezierCurve: Appearance.animation.scroll.bezierCurve
}
Behavior on weekDiff { Behavior on weekDiff {
id: weekScrollBehavior id: weekScrollBehavior
animation: Looks.transition.scroll.createObject(this) animation: root.scrollAnimation
} }
Timer { Timer {
id: scrollAnimationCheckTimer id: scrollAnimationCheckTimer
@@ -56,12 +59,20 @@ Item {
scrollAnimationCheckTimer.restart(); scrollAnimationCheckTimer.restart();
} }
MouseArea { property var wheelAction: (wheel) => {
anchors.fill: parent // Reverse cuz scrolling down should advance
onWheel: wheel => { const sign = wheel.angleDelta.y / 120 * -1;
root.targetWeekDiff += wheel.angleDelta.y / 120 * -root.weeksPerScroll; // Reverse cuz scrolling down should advance if (Config.options.calendar.weekScrollPrecision) {
root.targetWeekDiff += sign * root.weeksPerScroll;
} else {
scrollMonthsAndSnap(sign);
} }
} }
MouseArea {
id: mouseArea
anchors.fill: parent
onWheel: (wheel) => root.wheelAction(wheel)
}
// Date calculations // Date calculations
readonly property int millisPerWeek: 7 * 24 * 60 * 60 * 1000 readonly property int millisPerWeek: 7 * 24 * 60 * 60 * 1000
@@ -82,14 +93,16 @@ Item {
return DateUtils.getIthDayDateOfSameWeek(dateInTargetWeek, root.focusDayOfWeekIndex - root.locale.firstDayOfWeek, root.locale.firstdayOfWeek); // 4 = Thursday return DateUtils.getIthDayDateOfSameWeek(dateInTargetWeek, root.focusDayOfWeekIndex - root.locale.firstDayOfWeek, root.locale.firstdayOfWeek); // 4 = Thursday
} }
property int focusedMonth: focusedDate.getMonth() + 1 // 0-indexed -> 1-indexed property int focusedMonth: focusedDate.getMonth() + 1 // 0-indexed -> 1-indexed
property string title: locale.toString(focusedDate, "MMMM yyyy")
// Sizes // Sizes
property real verticalPadding: 0 property real verticalPadding: 0
property real horizontalPadding: 0
property real buttonSize: 40 property real buttonSize: 40
property real buttonSpacing: 2 property real buttonSpacing: 2
property real buttonVerticalSpacing: buttonSpacing property real buttonVerticalSpacing: buttonSpacing
implicitHeight: (6 * buttonSize) + (5 * buttonVerticalSpacing) + (2 * verticalPadding) implicitHeight: (6 * buttonSize) + (5 * buttonVerticalSpacing) + (2 * verticalPadding)
implicitWidth: weeksColumn.implicitWidth implicitWidth: weeksColumn.implicitWidth + (2 * horizontalPadding)
clip: true clip: true
ColumnLayout { ColumnLayout {
@@ -97,6 +110,8 @@ Item {
anchors { anchors {
left: parent.left left: parent.left
right: parent.right right: parent.right
leftMargin: root.horizontalPadding
rightMargin: root.horizontalPadding
} }
y: { y: {
const spacePerExtraRow = root.buttonSize + root.buttonVerticalSpacing; const spacePerExtraRow = root.buttonSize + root.buttonVerticalSpacing;
@@ -0,0 +1,41 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
GridLayout {
id: root
columns: 1
property real totalDuration: 250
property real interval: totalDuration / count
property list<QtObject> choreographableChildren: children.filter(c => {
return c.hasOwnProperty("progress")
})
readonly property int count: choreographableChildren.length
property bool shown: false
onShownChanged: {
// When hiding, hide all at once
if (!shown) {
for (var i = 0; i < count; i++) {
choreographableChildren[i].progress = 0;
}
}
// When showing, choreograph
root.choreographIndex = 0;
}
property int choreographIndex: count
Timer {
id: choreographTimer
interval: root.interval
property bool step: root.shown && root.choreographIndex < root.count
running: step
repeat: step
onTriggered: {
const index = root.choreographIndex;
root.choreographableChildren[index].progress = 1;
root.choreographIndex++;
}
}
}
@@ -0,0 +1,7 @@
pragma ComponentBehavior: Bound
import QtQuick
FadeLoader {
id: root
onActiveChanged: if (active) item.shown = true
}
@@ -1,7 +1,7 @@
import QtQuick import QtQuick
Rectangle { Rectangle {
property double diameter property real diameter
implicitWidth: diameter implicitWidth: diameter
implicitHeight: diameter implicitHeight: diameter
@@ -42,8 +42,7 @@ Item {
active: root.fill active: root.fill
anchors.fill: parent anchors.fill: parent
sourceComponent: Rectangle { sourceComponent: Circle {
radius: 9999
color: root.colSecondary color: root.colSecondary
} }
} }
@@ -3,7 +3,7 @@ import qs.modules.common.functions
import qs.modules.common.widgets import qs.modules.common.widgets
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Qt5Compat.GraphicalEffects import QtQuick.Effects
/** /**
* A progress bar with both ends rounded and text acts as clipping like OneUI 7's battery indicator. * A progress bar with both ends rounded and text acts as clipping like OneUI 7's battery indicator.
@@ -16,15 +16,17 @@ ProgressBar {
property color highlightColor: Appearance?.colors.colOnSecondaryContainer ?? "#685496" property color highlightColor: Appearance?.colors.colOnSecondaryContainer ?? "#685496"
property color trackColor: ColorUtils.transparentize(highlightColor, 0.5) ?? "#F1D3F9" property color trackColor: ColorUtils.transparentize(highlightColor, 0.5) ?? "#F1D3F9"
property alias radius: contentItem.radius property alias radius: contentItem.radius
property alias progressRadius: progressFill.radius
property string text property string text
default property Item textMask: Item { default property Item textMask: Item {
width: valueBarWidth width: root.valueBarWidth
height: valueBarHeight height: root.valueBarHeight
StyledText { VisuallyCenteredStyledText {
anchors.centerIn: parent anchors.fill: parent
font: root.font font: root.font
text: root.text text: root.text
} }
layer.enabled: true
} }
text: Math.round(value * 100) text: Math.round(value * 100)
@@ -38,10 +40,9 @@ ProgressBar {
implicitWidth: valueBarWidth implicitWidth: valueBarWidth
} }
contentItem: Rectangle { contentItem: Pill {
id: contentItem id: contentItem
anchors.fill: parent anchors.fill: parent
radius: 9999
color: root.trackColor color: root.trackColor
visible: false visible: false
@@ -80,22 +81,40 @@ ProgressBar {
} }
} }
OpacityMask { Rectangle {
id: roundingMask id: contentMaskRect
anchors.fill: contentItem
width: contentItem.width
height: contentItem.height
radius: contentItem.radius
layer.enabled: true
visible: false visible: false
anchors.fill: parent
source: contentItem
maskSource: Rectangle {
width: contentItem.width
height: contentItem.height
radius: contentItem.radius
}
} }
OpacityMask { Item {
anchors.fill: parent // textMask has to be rendered somewhere so we put it in a practically invisible item
source: roundingMask anchors.centerIn: parent
invert: true opacity: 0
maskSource: root.textMask Component.onCompleted: root.textMask.layer.enabled = true // for multieffect masking
children: [root.textMask]
} }
MaskMultiEffect {
id: boxClip
anchors.fill: parent
source: contentItem
maskSource: contentMaskRect
visible: false
}
MaskMultiEffect {
id: textClip
anchors.fill: parent
implicitWidth: contentItem.implicitWidth
implicitHeight: contentItem.implicitHeight
source: boxClip
maskSource: root.textMask
maskInverted: true
}
} }
@@ -0,0 +1,14 @@
import QtQuick
import QtQuick.Effects
import qs.modules.common
MultiEffect {
property color sourceColor: "black"
colorization: 1
brightness: 1 - sourceColor.hslLightness
Behavior on colorizationColor {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
@@ -0,0 +1,75 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Shapes
import qs.modules.common
AbstractCombinedProgressBar {
id: root
property int implicitSize: 30
property int lineWidth: 2
property real gapAngle: 360 / 18
valueHighlights: [Appearance.colors.colPrimary, Appearance.colors.colTertiary]
valueTroughs: [Appearance.colors.colSecondaryContainer, Appearance.colors.colTertiaryContainer]
property bool enableAnimation: true
property int animationDuration: 800
property var easingType: Easing.OutCubic
implicitWidth: implicitSize
implicitHeight: implicitSize
readonly property real centerX: root.width / 2
readonly property real centerY: root.height / 2
readonly property real arcRadius: root.implicitSize / 2 - root.lineWidth
readonly property real startAngle: -90
background: Item {
implicitWidth: root.implicitSize
implicitHeight: root.implicitSize
}
function isNegligibleSegment(seg: var): bool {
const range = seg[1] - seg[0];
return range < 1 / 360; // TODO make this less arbitrary
}
Repeater {
model: root.visualSegments
delegate: Shape {
id: segShape
required property int index
required property var modelData
property bool negligible: root.isNegligibleSegment(modelData)
property bool atStart: index == 0
property bool atEnd: index == root.visualSegments.length - 1
property real displaySegStart: {
var i = index;
while ((i > 0 && root.isNegligibleSegment(root.visualSegments[i - 1])))
i--;
return root.visualSegments[i][0];
}
anchors.fill: parent
layer.enabled: true
layer.smooth: true
preferredRendererType: Shape.CurveRenderer
ShapePath {
strokeColor: segShape.negligible ? "transparent" : root.segmentColors[segShape.index % root.segmentColors.length]
strokeWidth: segShape.negligible ? 0 : root.lineWidth
capStyle: ShapePath.RoundCap
fillColor: "transparent"
PathAngleArc {
centerX: root.centerX
centerY: root.centerY
radiusX: root.arcRadius
radiusY: root.arcRadius
startAngle: root.startAngle + 360 * segShape.displaySegStart + root.gapAngle / 2
sweepAngle: 360 * (segShape.modelData[1] - segShape.displaySegStart) - root.gapAngle
}
}
}
}
}
@@ -23,6 +23,10 @@ Flow {
] ]
property var currentValue: null property var currentValue: null
function focusSelectedChild() {
children.find(c => c.value == currentValue).forceActiveFocus()
}
signal selected(var newValue) signal selected(var newValue)
Repeater { Repeater {
@@ -31,6 +35,7 @@ Flow {
id: paletteButton id: paletteButton
required property var modelData required property var modelData
required property int index required property int index
readonly property var value: modelData.value
onYChanged: { onYChanged: {
if (index === 0) { if (index === 0) {
paletteButton.leftmost = true paletteButton.leftmost = true
@@ -33,6 +33,7 @@ RippleButton {
} }
StyledSwitch { StyledSwitch {
id: switchWidget id: switchWidget
focusPolicy: Qt.NoFocus
down: root.down down: root.down
Layout.fillWidth: false Layout.fillWidth: false
checked: root.checked checked: root.checked
@@ -0,0 +1,25 @@
import QtQuick
import Quickshell
import qs.modules.common
Item {
id: root
property alias load: loader.activeAsync
property bool shown: true // By default show immediately when loaded
property alias component: loader.component
property alias fade: opacityBehavior.enabled
property alias animation: opacityBehavior.animation
opacity: loader.active && shown ? 1 : 0
visible: opacity > 0
Behavior on opacity {
id: opacityBehavior
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
LazyLoader {
id: loader
}
}
@@ -0,0 +1,25 @@
import QtQuick
Loader {
id: root
property int fallbackIndex: 0
property list<url> fallbacks: []
property list<Component> fallbackComponents: []
onStatusChanged: {
if (status === Loader.Error && fallbackIndex < fallbacks.length) {
if (fallbacks[fallbackIndex]) {
source = fallbacks[fallbackIndex];
if (fallbackComponents[fallbackIndex]) {
console.warn("[FallbackLoader] Both fallbacks urls and components are set, using url fallback");
}
} else if (fallbackComponents[fallbackIndex]) {
sourceComponent = fallbackComponents[fallbackIndex];
} else {
console.error("[FallbackLoader] Out of fallbacks, tried all", fallbackIndex);
}
fallbackIndex += 1;
}
}
}
@@ -0,0 +1,16 @@
import QtQuick
Item {
id: root
property alias longestText: longestTextMetrics.text
property alias font: longestTextMetrics.font
implicitWidth: longestTextMetrics.width
implicitHeight: longestTextMetrics.height
TextMetrics {
id: longestTextMetrics
text: root.longestText
}
}
@@ -0,0 +1,26 @@
pragma ComponentBehavior: Bound
import QtQuick
AbstractChoreographable {
id: root
progress: 0
property bool vertical: true
property bool reverseDirection: false
property real distance: 15
readonly property real directionMultiplier: reverseDirection ? -1 : 1
Component.onCompleted: syncProgress()
onProgressChanged: syncProgress()
function syncProgress() {
const progressDistance = distance * (1 - progress) * directionMultiplier;
root.child.opacity = progress
if (vertical) {
root.child.y = progressDistance
} else {
root.child.x = progressDistance
}
}
}
@@ -42,6 +42,7 @@ Button {
property color colBackgroundToggled: Appearance?.colors.colPrimary ?? "#65558F" property color colBackgroundToggled: Appearance?.colors.colPrimary ?? "#65558F"
property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C" property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C"
property color colBackgroundToggledActive: Appearance?.colors.colPrimaryActive ?? "#D6CEE2" property color colBackgroundToggledActive: Appearance?.colors.colPrimaryActive ?? "#D6CEE2"
property color colFocusRing: Appearance.colors.colOnSecondaryContainer
property real radius: root.down ? root.buttonRadiusPressed : root.buttonRadius property real radius: root.down ? root.buttonRadiusPressed : root.buttonRadius
property real leftRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius property real leftRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius
@@ -117,7 +118,6 @@ Button {
}; };
} }
property bool tabbedTo: root.focus && (focusReason === Qt.TabFocusReason || focusReason === Qt.BacktabFocusReason)
background: Rectangle { background: Rectangle {
id: buttonBackground id: buttonBackground
topLeftRadius: root.leftRadius topLeftRadius: root.leftRadius
@@ -130,9 +130,25 @@ Button {
Behavior on color { Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
} }
}
border.width: root.tabbedTo ? 2 : 0 z: visualFocus ? 1 : 0
border.color: Appearance.colors.colSecondary Rectangle {
id: focusRing
topLeftRadius: root.leftRadius - anchors.margins
topRightRadius: root.rightRadius - anchors.margins
bottomLeftRadius: root.leftRadius - anchors.margins
bottomRightRadius: root.rightRadius - anchors.margins
visible: root.visualFocus
color: "transparent"
anchors {
fill: parent
margins: -4
}
border {
color: root.colFocusRing
width: 2
}
} }
contentItem: StyledText { contentItem: StyledText {
@@ -20,11 +20,12 @@ StyledText {
} }
} }
property Animation fillAnimation: NumberAnimation {
duration: Appearance?.animation.elementMoveFast.duration ?? 200
easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline
easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]
}
Behavior on fill { // Leaky leaky, no good Behavior on fill { // Leaky leaky, no good
NumberAnimation { animation: root.fillAnimation
duration: Appearance?.animation.elementMoveFast.duration ?? 200
easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline
easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]
}
} }
} }
@@ -85,7 +85,7 @@ MouseArea { // Notification group area
automaticallyReset: false automaticallyReset: false
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: { onPressed: (mouse) => {
if (mouse.button === Qt.RightButton) if (mouse.button === Qt.RightButton)
root.toggleExpanded(); root.toggleExpanded();
} }
@@ -0,0 +1,5 @@
import QtQuick
Rectangle {
radius: Math.min(width, height) / 2
}
@@ -0,0 +1,91 @@
import QtQuick
import qs.modules.common.models as M
import "shapes/material-shapes.js" as MaterialShapes
import "shapes/shapes/corner-rounding.js" as CornerRounding
import "shapes/geometry/offset.js" as Offset
// For returning the points
M.NestableObject {
id: root
required property real width
required property real height
property real radius: 0
property real topLeftRadius: radius
property real topRightRadius: radius
property real bottomLeftRadius: radius
property real bottomRightRadius: radius
property real xOffset: 0
property real yOffset: 0
readonly property real radiusLimit: Math.min(width, height) / 2
readonly property real effectiveTopLeftRadius: Math.min(topLeftRadius, radiusLimit)
readonly property real effectiveTopRightRadius: Math.min(topRightRadius, radiusLimit)
readonly property real effectiveBottomLeftRadius: Math.min(bottomLeftRadius, radiusLimit)
readonly property real effectiveBottomRightRadius: Math.min(bottomRightRadius, radiusLimit)
// Clockwise starting from bottom
property list<var> bottomPoints: [
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width - effectiveBottomRightRadius, yOffset + height), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width / 2, yOffset + height), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + effectiveBottomLeftRadius, yOffset + height), new CornerRounding.CornerRounding(0)),
]
property list<var> leftPoints: [
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + 0, yOffset + height - effectiveBottomLeftRadius), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + 0, yOffset + height / 2), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + 0, yOffset + effectiveTopLeftRadius), new CornerRounding.CornerRounding(0)),
]
property list<var> topPoints: [
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + effectiveTopLeftRadius, yOffset + 0), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width / 2, yOffset + 0), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width - effectiveTopRightRadius, yOffset + 0), new CornerRounding.CornerRounding(0)),
]
property list<var> rightPoints: [
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width, yOffset + effectiveTopRightRadius), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width, yOffset + height / 2), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width, yOffset + height - effectiveBottomRightRadius), new CornerRounding.CornerRounding(0)),
]
function getFirstBottomPoints() {
return bottomPoints.slice(Math.floor(bottomPoints.length / 2))
}
function getLastBottomPoints() {
return bottomPoints.slice(0, Math.floor(bottomPoints.length / 2))
}
function getBottomLeftPoint(extraXOffset = 0, extraYOffset = 0, radius = undefined) {
if (radius === undefined) radius = effectiveBottomLeftRadius;
return new MaterialShapes.PointNRound(new Offset.Offset(xOffset + extraXOffset + 0, yOffset + extraYOffset + height), new CornerRounding.CornerRounding(radius))
}
function getTopLeftPoint(extraXOffset = 0, extraYOffset = 0, radius = undefined) {
if (radius === undefined) radius = effectiveTopLeftRadius;
return new MaterialShapes.PointNRound(new Offset.Offset(xOffset + extraXOffset + 0, yOffset + extraYOffset + 0), new CornerRounding.CornerRounding(radius))
}
function getTopRightPoint(extraXOffset = 0, extraYOffset = 0, radius = undefined) {
if (radius === undefined) radius = effectiveTopRightRadius;
return new MaterialShapes.PointNRound(new Offset.Offset(xOffset + extraXOffset + width, yOffset + extraYOffset + 0), new CornerRounding.CornerRounding(radius))
}
function getBottomRightPoint(extraXOffset = 0, extraYOffset = 0, radius = undefined) {
if (radius === undefined) radius = effectiveBottomRightRadius;
return new MaterialShapes.PointNRound(new Offset.Offset(xOffset + extraXOffset + width, yOffset + extraYOffset + height), new CornerRounding.CornerRounding(radius))
}
function getFullShape() {
const points = [
...getFirstBottomPoints(),
getBottomLeftPoint(),
...leftPoints,
getTopLeftPoint(),
...topPoints,
getTopRightPoint(),
...rightPoints,
getBottomRightPoint(),
...getLastBottomPoints(),
]
return MaterialShapes.customPolygon(points);
}
}
@@ -29,6 +29,7 @@ Button {
property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C" property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C"
property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2" property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2"
property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2" property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2"
property color colFocusRing: Appearance.colors.colOnSecondaryContainer
opacity: root.enabled ? 1 : 0.4 opacity: root.enabled ? 1 : 0.4
property color buttonColor: ColorUtils.transparentize(root.toggled ? property color buttonColor: ColorUtils.transparentize(root.toggled ?
@@ -180,6 +181,22 @@ Button {
} }
} }
z: visualFocus ? 1 : 0
Rectangle {
id: focusRing
radius: buttonBackground.radius - anchors.margins
visible: root.visualFocus
color: "transparent"
anchors {
fill: parent
margins: -4
}
border {
color: root.colFocusRing
width: 2
}
}
contentItem: StyledText { contentItem: StyledText {
text: root.buttonText text: root.buttonText
} }
@@ -0,0 +1,19 @@
import QtQuick
Rectangle {
id: root
// https://m3.material.io/foundations/interaction/states/state-layers
enum State {
Hover, Focus, Press, Drag
}
property var state: StateLayer.State.Hover
opacity: switch(state) {
case StateLayer.State.Hover: return 0.08;
case StateLayer.State.Focus: return 0.1;
case StateLayer.State.Press: return 0.1;
case StateLayer.State.Drag: return 0.16;
default: return 0;
}
}
@@ -0,0 +1,66 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.modules.common as C
Rectangle {
id: root
property bool hover: false
property bool press: false
property bool drag: false
property color contentColor: C.Appearance.m3colors.m3onBackground
color: "transparent"
FadeLoader {
id: hoverLoader
anchors.fill: parent
shown: root.hover
sourceComponent: StateLayer {
state: StateLayer.State.Hover
color: root.contentColor
topLeftRadius: root.topLeftRadius
topRightRadius: root.topRightRadius
bottomLeftRadius: root.bottomLeftRadius
bottomRightRadius: root.bottomRightRadius
}
}
FadeLoader {
id: focusLoader
anchors.fill: parent
shown: root.focus
sourceComponent: StateLayer {
state: StateLayer.State.Focus
color: root.contentColor
topLeftRadius: root.topLeftRadius
topRightRadius: root.topRightRadius
bottomLeftRadius: root.bottomLeftRadius
bottomRightRadius: root.bottomRightRadius
}
}
FadeLoader {
id: pressLoader
anchors.fill: parent
shown: root.press
sourceComponent: StateLayer {
state: StateLayer.State.Press
color: root.contentColor
topLeftRadius: root.topLeftRadius
topRightRadius: root.topRightRadius
bottomLeftRadius: root.bottomLeftRadius
bottomRightRadius: root.bottomRightRadius
}
}
FadeLoader {
id: dragLoader
anchors.fill: parent
shown: root.drag
sourceComponent: StateLayer {
state: StateLayer.State.Drag
color: root.contentColor
topLeftRadius: root.topLeftRadius
topRightRadius: root.topRightRadius
bottomLeftRadius: root.bottomLeftRadius
bottomRightRadius: root.bottomRightRadius
}
}
}
@@ -0,0 +1,81 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import ".."
import "../functions"
Button {
id: root
property alias radius: bg.radius
property alias contentLayer: bg.contentLayer
property color colFocusRing: Appearance.colors.colOnSecondaryContainer
property color colBackground: checked ? colBackgroundChecked : colBackgroundUnchecked
property color colForeground: checked ? colForegroundChecked : colForegroundUnchecked
property color colBackgroundUnchecked: ColorUtils.transparentize(Appearance.colors.colLayer4Base, 1)
property color colBackgroundChecked: Appearance.colors.colPrimary
property color colForegroundUnchecked: Appearance.colors.colOnLayer4
property color colForegroundChecked: Appearance.colors.colOnPrimary
hoverEnabled: true
opacity: root.enabled ? 1 : 0.5
Behavior on colBackground {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
Behavior on colForeground {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
HoverHandler {
cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
}
background: StyledRectangle {
id: bg
implicitHeight: root.contentItem.implicitHeight
implicitWidth: root.contentItem.implicitWidth
radius: Math.min(width, height) / 2
color: root.colBackground
StateOverlay {
anchors.fill: parent
hover: root.hovered && root.enabled
press: root.pressed && root.enabled
focus: false // We use a ring instead
radius: bg.radius
}
Rectangle {
id: focusRing
radius: bg.radius - anchors.margins
visible: root.visualFocus
color: "transparent"
anchors {
fill: parent
margins: -4
}
border {
color: root.colFocusRing
width: 2
}
}
}
z: visualFocus ? 1 : 0
contentItem: Item {
implicitWidth: buttonText.implicitWidth
implicitHeight: buttonText.implicitHeight
VisuallyCenteredStyledText {
id: buttonText
anchors.centerIn: parent
text: root.text
color: root.colForeground
}
}
}
@@ -0,0 +1,73 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.modules.common
AbstractCombinedProgressBar {
id: root
property real valueBarWidth: 120
property real valueBarHeight: 4
property real valueBarGap: 4
property real valueBarInnerRadius: Appearance.rounding.unsharpen
valueHighlights: [Appearance.colors.colPrimary, Appearance.colors.colTertiary]
valueTroughs: [Appearance.colors.colSecondaryContainer, Appearance.colors.colTertiaryContainer]
background: Item {
implicitWidth: root.valueBarWidth
implicitHeight: root.valueBarHeight
}
// "negligible" = too small that it'd look weird when shown
function isNegligibleSegment(seg: var): bool {
const wdth = seg[1] - seg[0];
const visualWidth = availableWidth * wdth;
return (visualWidth <= valueBarGap + valueBarHeight)
}
contentItem: Item {
Repeater {
model: root.visualSegments
delegate: Rectangle {
required property int index
required property var modelData
visible: !root.isNegligibleSegment(modelData)
property bool atStart: index == 0
property bool atEnd: index == root.visualSegments.length - 1
property real displaySegStart: { // swallow previous segments if they're "negligible"
var i = index;
while ((i > 0 && root.isNegligibleSegment(root.visualSegments[i-1])))
i--;
return root.visualSegments[i][0]
}
anchors {
top: parent.top
bottom: parent.bottom
}
x: {
var result = root.availableWidth * displaySegStart;
if (!atStart) result += root.valueBarGap / 2;
return result;
}
width: {
var result = root.availableWidth * (modelData[1] - displaySegStart)
if (atStart || atEnd) result -= root.valueBarGap / 2;
else result -= root.valueBarGap;
return result;
}
color: root.segmentColors[index % root.segmentColors.length]
property real startRadius: atStart ? height / 2 : root.valueBarInnerRadius
property real endRadius: atEnd ? height / 2 : root.valueBarInnerRadius
topLeftRadius: startRadius
bottomLeftRadius: startRadius
topRightRadius: endRadius
bottomRightRadius: endRadius
}
}
}
}
@@ -0,0 +1,19 @@
pragma ComponentBehavior: Bound
import QtQuick
StyledButton {
id: root
property alias implicitSize: root.implicitHeight
implicitWidth: implicitHeight
property alias iconSize: icon.iconSize
contentItem: Item {
MaterialSymbol {
id: icon
anchors.centerIn: parent
color: root.colForeground
text: root.text
}
}
}
@@ -0,0 +1,22 @@
import QtQuick
import qs.modules.common as C
// This is to enable future fancy styles for rectangles. Some ideas:
// - normal rounded rect
// - osk.sh
// - 3d
// i hope i actually get to this and not shrimply forget
// aaaaa i realized for this to work i would have to make this for shapes in general not just rects
Rectangle {
enum ContentLayer { Background, Pane, Group, Subgroup, Control }
property var contentLayer: StyledRectangle.ContentLayer.Pane // To appropriately add effects like shadows/3d-ization
color: switch(contentLayer) {
case StyledRectangle.ContentLayer.Background: C.Appearance.colors.colLayer0;
case StyledRectangle.ContentLayer.Pane: C.Appearance.colors.colLayer1;
case StyledRectangle.ContentLayer.Group: C.Appearance.colors.colLayer2;
case StyledRectangle.ContentLayer.Subgroup: C.Appearance.colors.colLayer3;
case StyledRectangle.ContentLayer.Control: C.Appearance.colors.colLayer4;
default: C.Appearance.colors.colLayer1;
}
}
@@ -23,7 +23,7 @@ Text {
component Anim: NumberAnimation { component Anim: NumberAnimation {
target: root target: root
duration: 300 / 2 duration: 130
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
} }
@@ -55,7 +55,8 @@ Text {
Anim { Anim {
property: "opacity" property: "opacity"
to: 0 to: 0
easing.type: Easing.InSine easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
} }
} }
PropertyAction {} // Tie the text update to this point (we don't want it to happen during the first slide+fade) PropertyAction {} // Tie the text update to this point (we don't want it to happen during the first slide+fade)
@@ -83,7 +84,8 @@ Text {
Anim { Anim {
property: "opacity" property: "opacity"
to: 1 to: 1
easing.type: Easing.OutSine easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
} }
} }
} }
@@ -0,0 +1,17 @@
import QtQuick
import qs.modules.common as C
FallbackLoader {
id: root
required property string componentName
property string context // Path for the builtin component
readonly property string componentNameWithExt: componentName.endsWith(".qml") ? componentName : `${componentName}.qml`
source: `${C.Directories.userComponents}/${componentNameWithExt}`
fallbacks: [
...(context ? [ `${context}/${componentNameWithExt}` ] : []),
componentNameWithExt
]
}
@@ -0,0 +1,45 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
Item {
id: root
property alias textWidget: textWidget
property alias text: textWidget.text
property alias horizontalAlignment: textWidget.horizontalAlignment
property alias verticalAlignment: textWidget.verticalAlignment
property alias font: textWidget.font
property alias color: textWidget.color
property alias elide: textWidget.elide
property alias wrapMode: textWidget.wrapMode
property alias animateChange: textWidget.animateChange
// In many cases the baseline is a bit high to accomodate the dangling parts of "g" and "y",
// making most text (especiall number-only text) not well-balanced.
// This adjusts the rounding to make sure the text gets lowered if not internally pixel-aligned
property bool lowerBias: true
implicitWidth: textMetrics.width
implicitHeight: textMetrics.height
TextMetrics {
id: textMetrics
font: root.font
text: root.text
}
StyledText {
id: textWidget
anchors {
left: parent.left
right: parent.right
}
y: {
const value = (parent.height - textMetrics.height) / 2;
return root.lowerBias ? Math.ceil(value) : Math.round(value);
}
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
@@ -0,0 +1,73 @@
import QtQuick
import Quickshell
import qs.services as S
/**
* Abstract morphed panel to be used in TopLayerPanel.
* Screen width and height are to be supplied when declared in the top layer panel
* Others are to be declared by panels deriving from this
*
* To make sure morph movements don't look weird:
* - Follow the convention of having points start from bottom-middle and go clockwise
* - Make sure the number of points is "balanced" in all directions
* - Tip: Sometimes symmetry is not enough. Try to have more intermediate points if ones you have are too spaced out and act funny.
*/
Item {
id: root
// To be fed
property int screenWidth: QsWindow.window.width
property int screenHeight: QsWindow.window.height
// Signals & loading
signal requestFocus()
signal dismissed()
signal focusGrabDismissed()
property bool load: true
property bool shown: true
// Some info
property int reservedTop: 0
property int reservedBottom: 0
property int reservedLeft: 0
property int reservedRight: 0
// Main stuff
property var backgroundPolygon
property list<Item> baseMaskItems: [root]
property list<Item> attachedMaskItems: []
property list<Item> maskItems: [...baseMaskItems, ...attachedMaskItems]
property Region maskRegion: Region {
regions: root.maskItems.map(item => regionComp.createObject(this, { "item": item }))
}
function addAttachedMaskItem(item) {
if (root.attachedMaskItems.includes(item)) return;
root.attachedMaskItems.push(item);
}
function removeAttachedMaskItem(item) {
root.attachedMaskItems = root.attachedMaskItems.filter(i => i !== item);
}
onAttachedMaskItemsChanged: {
if (attachedMaskItems.length > 0) {
S.GlobalFocusGrab.addDismissable(root.QsWindow.window);
} else {
S.GlobalFocusGrab.removeDismissable(root.QsWindow.window);
}
}
Connections {
target: S.GlobalFocusGrab
function onDismissed() {
root.attachedMaskItems = [];
root.focusGrabDismissed();
}
}
Component {
id: regionComp
Region {}
}
}
@@ -0,0 +1,94 @@
import QtQuick
import Quickshell.Hyprland
import qs
import qs.modules.common
import "../../common/widgets/shapes/material-shapes.js" as MaterialShapes
import "../../common/widgets/shapes/shapes/corner-rounding.js" as CornerRounding
import "../../common/widgets/shapes/geometry/offset.js" as Offset
HAbstractMorphedPanel {
id: root
// Own props
property int edgeGap: Appearance.sizes.hyprlandGapsOut
property real rounding: Appearance.rounding.windowRounding
property real contentHeight: 300 // For now
property real contentWidth: root.screenWidth * 0.9
property real horizontalGap: (root.screenWidth - contentWidth) / 2
// Background
backgroundPolygon: {
const bottom = Config.options.bar.bottom
const topY = bottom ? (root.screenHeight - edgeGap - contentHeight) : edgeGap
const bottomY = bottom ? (root.screenHeight - edgeGap) : (edgeGap + contentHeight)
const points = [
// bottom-middle
new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth / 2, bottomY), new CornerRounding.CornerRounding(0)),
// bottom-left
new MaterialShapes.PointNRound(new Offset.Offset(horizontalGap + rounding, bottomY), new CornerRounding.CornerRounding(rounding)),
new MaterialShapes.PointNRound(new Offset.Offset(horizontalGap, bottomY), new CornerRounding.CornerRounding(rounding)),
new MaterialShapes.PointNRound(new Offset.Offset(horizontalGap, bottomY - rounding), new CornerRounding.CornerRounding(rounding)),
// top-left
new MaterialShapes.PointNRound(new Offset.Offset(horizontalGap, topY + rounding), new CornerRounding.CornerRounding(rounding)),
new MaterialShapes.PointNRound(new Offset.Offset(horizontalGap, topY), new CornerRounding.CornerRounding(rounding)),
new MaterialShapes.PointNRound(new Offset.Offset(horizontalGap + rounding, topY), new CornerRounding.CornerRounding(rounding)),
// top-middle
new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth / 2, topY), new CornerRounding.CornerRounding(0)),
// top-right
new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - horizontalGap - rounding, topY), new CornerRounding.CornerRounding(rounding)),
new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - horizontalGap, topY), new CornerRounding.CornerRounding(rounding)),
new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - horizontalGap, topY + rounding), new CornerRounding.CornerRounding(rounding)),
// bottom-right
new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - horizontalGap, bottomY - rounding), new CornerRounding.CornerRounding(rounding)),
new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - horizontalGap, bottomY), new CornerRounding.CornerRounding(rounding)),
new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - horizontalGap - rounding, bottomY), new CornerRounding.CornerRounding(rounding)),
]
return MaterialShapes.customPolygon(points, 1, new Offset.Offset(root.screenWidth / 2, edgeGap + contentHeight / 2))
}
// // Keybinds
// GlobalShortcut {
// name: "searchToggle"
// description: "Toggles search on press"
// onPressed: {
// GlobalStates.overviewOpen = !GlobalStates.overviewOpen;
// }
// }
// GlobalShortcut {
// name: "searchToggleRelease"
// description: "Toggles search on release"
// onPressed: {
// GlobalStates.superReleaseMightTrigger = true;
// }
// onReleased: {
// if (!GlobalStates.superReleaseMightTrigger) {
// GlobalStates.superReleaseMightTrigger = true;
// return;
// }
// GlobalStates.overviewOpen = !GlobalStates.overviewOpen;
// }
// }
// GlobalShortcut {
// name: "searchToggleReleaseInterrupt"
// description: "Interrupts possibility of search being toggled on release. " + "This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. " + "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything."
// onPressed: {
// GlobalStates.superReleaseMightTrigger = false;
// }
// }
// Connections {
// target: GlobalStates
// function onOverviewOpenChanged() {
// if (GlobalStates.overviewOpen) {
// root.requestFocus();
// } else {
// root.dismissed();
// }
// }
// }
}
@@ -0,0 +1,15 @@
import QtQuick
import Quickshell
// The stuff that sits on the "top" layer for layershells. Not to be confused with "toplevels" as in windows.
Scope {
id: root
Variants {
model: Quickshell.screens
delegate: HTopLayerPanel {
required property var modelData
screen: modelData
}
}
}
@@ -0,0 +1,170 @@
import QtQuick
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Wayland
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import "../../common"
import "../../common/widgets/shapes" as S
import "../../common/widgets/shapes/material-shapes.js" as MaterialShapes
import "../../common/widgets/shapes/shapes/corner-rounding.js" as CornerRounding
import "../../common/widgets/shapes/geometry/offset.js" as Offset
import "bar"
/**
* Fullscreen layer. Uses masking to not block clicks on windows n' stuff.
*/
PanelWindow {
id: root
///////////////// Window //////////////////
property real opacity: 1 * (GlobalStates.barOpen && !GlobalStates.screenLocked)
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
color: "transparent"
WlrLayershell.namespace: "quickshell:topLayerPanel"
exclusionMode: ExclusionMode.Ignore
anchors {
top: true
left: true
right: true
bottom: true
}
mask: root.currentPanel.maskRegion
// HyprlandWindow.visibleMask: mask // TODO: use this later to optimize hyprland's rendering
///////////////// Focus dim //////////////////
FadeLoader {
z: 0
anchors.fill: parent
shown: GlobalFocusGrab.active
sourceComponent: Rectangle {
opacity: 0.2
color: Appearance.m3colors.m3scrim
}
}
///////////////// Content //////////////////
property alias roundedPolygon: backgroundShape.roundedPolygon
property bool finishedMorphing: true
onRoundedPolygonChanged: finishedMorphing = false
Connections {
target: backgroundShape
function onProgressChanged() {
// While it overshoots because of the spring animation, waiting for the bounce to finish entirely would be too slow
// ^ (totally not an excuse for my laziness)
if (backgroundShape.progress >= 1.0) {
root.finishedMorphing = true;
}
}
}
S.ShapeCanvas {
id: backgroundShape
z: 1
anchors.fill: parent
polygonIsNormalized: false
roundedPolygon: MaterialShapes.customPolygon([new MaterialShapes.PointNRound(new Offset.Offset(root.screen.width, 0), new CornerRounding.CornerRounding(9999)),])
animation: NumberAnimation {
duration: 500
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveDefaultSpatial
}
color: Appearance.colors.colLayer0
opacity: root.opacity
borderWidth: (root.currentPanel === bar && Config.options.bar.cornerStyle !== 1) ? 0 : 1
borderColor: Appearance.colors.colLayer0Border
visible: false // cuz there's already the shadow
// debug: true
}
DropShadow {
id: shadow
opacity: root.opacity
source: backgroundShape
anchors.fill: backgroundShape
radius: 10
samples: radius * 2 + 1 // Ideally radius * 2 + 1, see qt docs
color: "#44000000"
}
property HAbstractMorphedPanel currentPanel: null
Component.onCompleted: currentPanel = bar
roundedPolygon: currentPanel.backgroundPolygon
// Do we want to have reserved area always follow the bar or maybe differ per panel?
EdgeReservedArea {
anchors.top: true
exclusiveZone: bar.reservedTop
}
EdgeReservedArea {
anchors.bottom: true
exclusiveZone: bar.reservedBottom
}
EdgeReservedArea {
anchors.left: true
exclusiveZone: bar.reservedLeft
}
EdgeReservedArea {
anchors.right: true
exclusiveZone: bar.reservedRight
}
////////////// Content: Panels ///////////////
function dismiss() {
root.currentPanel = bar;
}
Item {
id: panelRootItem
anchors.fill: parent
Connections {
target: GlobalFocusGrab
function onActiveChanged() {
if (GlobalFocusGrab.active) {
panelRootItem.focus = true
}
}
}
Keys.onPressed: (event) => { // Esc to close
if (event.key === Qt.Key_Escape) {
GlobalFocusGrab.dismiss();
}
}
HBar {
id: bar
opacity: root.opacity
load: root.currentPanel === this && root.finishedMorphing // the extra condition is to prevent workspace widget from acting up when switching horizontal/vertical... should be fixed later
shown: root.finishedMorphing
}
// HOverview {
// id: overview
// opacity: root.opacity
// load: root.currentPanel === this
// shown: root.finishedMorphing
// onRequestFocus: root.currentPanel = overview
// onDismissed: root.dismiss()
// }
}
//////////////// Components /////////////////
component EdgeReservedArea: PanelWindow {
WlrLayershell.namespace: "quickshell:edgeReservedArea"
implicitWidth: 0
implicitHeight: 0
mask: Region {
item: null
}
screen: root.screen
}
}
@@ -0,0 +1,234 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.modules.common
import qs.modules.common.widgets
import "../../../common/widgets/shapes/material-shapes.js" as MaterialShapes
import "../../../common/widgets/shapes/shapes/corner-rounding.js" as CornerRounding
import "../../../common/widgets/shapes/geometry/offset.js" as Offset
import ".."
HAbstractMorphedPanel {
id: root
// Config
property bool vertical: Config.options.bar.vertical
property bool atBottom: Config.options.bar.bottom
property int cornerStyle: Config.options.bar.cornerStyle
// Own props
property int barHeight: Appearance.sizes.baseBarHeight
property int barVerticalWidth: Appearance.sizes.baseVerticalBarWidth
function getRounding(cornerStyle) {
switch(cornerStyle) {
case 0: return Appearance.rounding.screenRounding;
case 1: return Appearance.rounding.windowRounding;
case 2: return 0;
default: return Appearance.rounding.screenRounding;
}
}
function getEdgeGap(cornerStyle) {
switch(cornerStyle) {
case 0: return 0;
case 1: return Appearance.sizes.hyprlandGapsOut;
case 2: return 0;
default: return 0;
}
}
function getEdgeRounding(cornerStyle) {
switch(cornerStyle) {
case 0: return 0;
case 1: return Appearance.rounding.windowRounding;
case 2: return 0;
default: return Appearance.rounding.windowRounding;
}
}
function getHug(cornerStyle) {
return cornerStyle === 0;
}
property int reservedArea: (vertical ? barVerticalWidth : barHeight) + getEdgeGap(cornerStyle)
// Some info
reservedTop: (!atBottom && !vertical) ? reservedArea : 0
reservedBottom: (atBottom && !vertical) ? reservedArea : 0
reservedLeft: (!atBottom && vertical) ? reservedArea : 0
reservedRight: (atBottom && vertical) ? reservedArea : 0
// Background
function getBackgroundPolygon() {
// It's certainly cleaner to have the below props declared outside, but we do this
// to make sure a config change only makes this re-evaluate exactly once
const bottom = root.atBottom
const vertical = root.vertical
const cornerStyle = root.cornerStyle
const hug = root.getHug(cornerStyle)
const edgeGap = root.getEdgeGap(cornerStyle)
const edgeRounding = root.getEdgeRounding(cornerStyle)
const rounding = root.getRounding(cornerStyle)
const areaHeight = vertical ? root.screenHeight : (root.barHeight + edgeGap * 2)
const areaWidth = vertical ? (root.barVerticalWidth + edgeGap * 2) : root.screenWidth
const height = vertical ? (root.screenHeight - edgeGap * 2) : root.barHeight
const width = vertical ? root.barVerticalWidth : (root.screenWidth - edgeGap * 2)
const xLeft = (vertical && bottom) ? (root.screenWidth - edgeGap - width) : edgeGap
const xRight = (vertical && !bottom) ? (areaWidth - edgeGap) : (root.screenWidth - edgeGap)
const yTop = (!vertical && bottom) ? (root.screenHeight - edgeGap - height) : edgeGap
const yBottom = (!vertical && !bottom) ? (areaHeight - edgeGap) : (root.screenHeight - edgeGap)
const topLeftRounding = !bottom ? edgeRounding : rounding
const topRightRounding = !(bottom^vertical) ? edgeRounding : rounding
const bottomLeftRounding = !!(bottom^vertical) ? edgeRounding : rounding
const bottomRightRounding = bottom ? edgeRounding : rounding
var topCornerYDirection = 0, bottomCornerYDirection = 0, leftCornerXDirection = 0, rightCornerXDirection = 0;
if (vertical) {
topCornerYDirection = 1;
bottomCornerYDirection = -1;
} else if (cornerStyle === 2) { // Rect
topCornerYDirection = 0;
bottomCornerYDirection = 0;
} else if (cornerStyle === 1) { // Rounded
topCornerYDirection = 1;
bottomCornerYDirection = -1;
} else { // Hug
topCornerYDirection = bottom ? -1 : 1;
bottomCornerYDirection = bottom ? -1 : 1;
}
if (!vertical) {
leftCornerXDirection = 1;
rightCornerXDirection = -1
} else if (cornerStyle === 2) { // Rect
leftCornerXDirection = 0;
rightCornerXDirection = 0;
} else if (cornerStyle === 1) { // Rounded
leftCornerXDirection = 1;
rightCornerXDirection = -1;
} else { // Hug
leftCornerXDirection = bottom ? -1 : 1;
rightCornerXDirection = bottom ? -1 : 1;
}
var points = [
// bottom-middle
new MaterialShapes.PointNRound(new Offset.Offset(xLeft + width * 1/2, yBottom), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft + width * 0.1, yBottom), new CornerRounding.CornerRounding(0)),
// bottom-left
new MaterialShapes.PointNRound(new Offset.Offset(xLeft + rounding * leftCornerXDirection, yBottom), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft, yBottom), new CornerRounding.CornerRounding(bottomLeftRounding)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft, yBottom + rounding * bottomCornerYDirection), new CornerRounding.CornerRounding(edgeRounding)),
// middle-left
new MaterialShapes.PointNRound(new Offset.Offset(xLeft, yTop + height * 0.9), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft, yTop + height * 1/2), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft, yTop + height * 0.1), new CornerRounding.CornerRounding(0)),
// top-left
new MaterialShapes.PointNRound(new Offset.Offset(xLeft, yTop + rounding * topCornerYDirection), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft, yTop), new CornerRounding.CornerRounding(topLeftRounding)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft + rounding * leftCornerXDirection, yTop), new CornerRounding.CornerRounding(0)),
// top-middle
new MaterialShapes.PointNRound(new Offset.Offset(xLeft + width * 0.1, yTop), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft + width * 1/2, yTop), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft + width * 0.9, yTop), new CornerRounding.CornerRounding(0)),
// top-right
new MaterialShapes.PointNRound(new Offset.Offset(xRight + rounding * rightCornerXDirection, yTop), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xRight, yTop), new CornerRounding.CornerRounding(topRightRounding)),
new MaterialShapes.PointNRound(new Offset.Offset(xRight, yTop + rounding * topCornerYDirection), new CornerRounding.CornerRounding(0)),
// middle-right
new MaterialShapes.PointNRound(new Offset.Offset(xRight, yTop + height * 0.1), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xRight, yTop + height * 1/2), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xRight, yTop + height * 0.9), new CornerRounding.CornerRounding(0)),
// bottom-right
new MaterialShapes.PointNRound(new Offset.Offset(xRight, yBottom + rounding * bottomCornerYDirection), new CornerRounding.CornerRounding(edgeRounding)),
new MaterialShapes.PointNRound(new Offset.Offset(xRight, yBottom), new CornerRounding.CornerRounding(bottomRightRounding)),
new MaterialShapes.PointNRound(new Offset.Offset(xRight + rounding * rightCornerXDirection, yBottom), new CornerRounding.CornerRounding(0)),
// bottom-middle
new MaterialShapes.PointNRound(new Offset.Offset(xLeft + width * 0.9, yBottom), new CornerRounding.CornerRounding(0)),
]
return MaterialShapes.customPolygon(points, 1, new Offset.Offset(root.screenWidth / 2, edgeGap + barHeight / 2))
}
backgroundPolygon: getBackgroundPolygon()
Connections {
target: Config
function onReadyChanged() {
if (Config.ready)
root.backgroundPolygon = root.getBackgroundPolygon()
}
function onReloaded() {
root.extraLoadCondition = false
root.backgroundPolygon = root.getBackgroundPolygon()
root.extraLoadCondition = true
}
}
// Content
implicitHeight: vertical ? screenHeight : (barHeight + getEdgeGap(cornerStyle) * 2)
implicitWidth: vertical ? (barVerticalWidth + getEdgeGap(cornerStyle) * 2) : screenWidth
width: implicitWidth
height: implicitHeight
anchors {
top: parent.top
bottom: undefined
left: undefined
right: undefined
}
states: [
State {
name: "bottom"
when: root.atBottom && !root.vertical
AnchorChanges {
target: root
anchors.top: undefined
anchors.bottom: parent.bottom
anchors.left: undefined
anchors.right: undefined
}
},
State {
name: "left"
when: !root.atBottom && root.vertical
AnchorChanges {
target: root
anchors.top: undefined
anchors.bottom: undefined
anchors.left: parent.left
anchors.right: undefined
}
},
State {
name: "right"
when: root.atBottom && root.vertical
AnchorChanges {
target: root
anchors.top: undefined
anchors.bottom: undefined
anchors.left: undefined
anchors.right: parent.right
}
}
]
transitions: Transition {
AnchorAnimation {
duration: 500
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveDefaultSpatial
}
}
property bool extraLoadCondition: true
FadeLazyLoader {
id: contentLoader
load: root.load && root.extraLoadCondition
shown: root.shown && root.extraLoadCondition
anchors.fill: parent
component: HBarContent {
parent: contentLoader
anchors.fill: parent
}
}
}
@@ -0,0 +1,101 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.modules.common as C
import qs.modules.common.widgets as W
Item {
id: root
property bool vertical: C.Config.options.bar.vertical
property real spacing: 4
property list<var> leftWidgets: C.Config.options.hefty.bar.leftWidgets
property list<var> centerLeftWidgets: C.Config.options.hefty.bar.centerLeftWidgets
property list<var> centerWidgets: C.Config.options.hefty.bar.centerWidgets
property list<var> centerRightWidgets: C.Config.options.hefty.bar.centerRightWidgets
property list<var> rightWidgets: C.Config.options.hefty.bar.rightWidgets
Side {
id: leftSide
anchors.left: parent.left
anchors.top: parent.top
anchors.right: !root.vertical ? centerLeftSide.left : parent.right
anchors.bottom: !root.vertical ? parent.bottom : centerLeftSide.top
// For accessibility
anchors.leftMargin: 0
anchors.topMargin: 0
HBarUserFallbackComponentRepeater {
componentNames: root.leftWidgets
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
Side {
id: centerLeftSide
anchors.right: !root.vertical ? centerSide.left : parent.right
anchors.bottom: !root.vertical ? parent.bottom : centerSide.top
HBarUserFallbackComponentRepeater {
componentNames: [...root.centerLeftWidgets, ...(root.centerLeftWidgets.length > 0 ? [invisibleItem] : []),]
}
}
Side {
id: centerSide
anchors.verticalCenter: root.vertical ? parent.verticalCenter : undefined
anchors.horizontalCenter: !root.vertical ? parent.horizontalCenter : undefined
HBarUserFallbackComponentRepeater {
componentNames: [...(root.centerLeftWidgets.length > 0 ? [invisibleItem] : []), ...root.centerWidgets, ...(root.centerRightWidgets.length > 0 ? [invisibleItem] : []),]
}
}
Side {
id: centerRightSide
anchors.left: !root.vertical ? centerSide.right : parent.left
anchors.top: !root.vertical ? parent.top : centerSide.bottom
HBarUserFallbackComponentRepeater {
componentNames: [...(root.centerLeftWidgets.length > 0 ? [invisibleItem] : []), ...root.centerRightWidgets,]
}
}
Side {
id: rightSide
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.left: !root.vertical ? centerRightSide.right : parent.left
anchors.top: !root.vertical ? parent.top : centerRightSide.bottom
// For accessibility
anchors.rightMargin: 0
anchors.bottomMargin: 0
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
HBarUserFallbackComponentRepeater {
componentNames: root.rightWidgets
}
}
component Side: W.BoxLayout {
anchors {
top: !root.vertical ? parent.top : undefined
bottom: !root.vertical ? parent.bottom : undefined
topMargin: root.spacing * root.vertical
bottomMargin: root.spacing * root.vertical
left: root.vertical ? parent.left : undefined
right: root.vertical ? parent.right : undefined
leftMargin: root.spacing * !root.vertical
rightMargin: root.spacing * !root.vertical
}
vertical: C.Config.options.bar.vertical
spacing: root.spacing
}
}
@@ -0,0 +1,76 @@
import QtQuick
import QtQuick.Layouts
import qs.modules.common as C
import qs.modules.common.widgets as W
Item {
id: root
property bool startSide: false
property bool endSide: false
property alias color: bg.color
property real margins: 4
property real padding: 4
default property alias groupData: layout.data
readonly property bool vertical: C.Config.options.bar.vertical
readonly property bool m3eRadius: C.Config.options.hefty.bar.m3ExpressiveGrouping
readonly property real barUndirectionalWidth: C.Config.options.bar.vertical ? C.Appearance.sizes.baseVerticalBarWidth : C.Appearance.sizes.baseBarHeight
readonly property real backgroundUndirectionalWidth: barUndirectionalWidth - margins * 2
implicitWidth: vertical ? barUndirectionalWidth : layout.implicitWidth
implicitHeight: vertical ? layout.implicitHeight : barUndirectionalWidth
property alias startRadius: bg.startRadius
property alias endRadius: bg.endRadius
property alias topLeftRadius: bg.topLeftRadius
property alias topRightRadius: bg.topRightRadius
property alias bottomLeftRadius: bg.bottomLeftRadius
property alias bottomRightRadius: bg.bottomRightRadius
property real backgroundWidth: root.vertical ? root.backgroundUndirectionalWidth : (root.width - margins * (startSide + endSide))
property real backgroundHeight: !root.vertical ? root.backgroundUndirectionalWidth : (root.height - margins * (startSide + endSide))
property real backgroundTopMargin: root.margins * (!root.vertical || root.startSide)
property real backgroundLeftMargin: root.margins * (root.vertical || root.startSide)
property real fullBackgroundRadius: Math.min(backgroundWidth, backgroundHeight) / 2
function getBackgroundRadius(atSide) {
if (root.m3eRadius) {
if (atSide) return fullBackgroundRadius;
else return C.Appearance.rounding.unsharpenmore;
} else {
return 12;
}
}
property Item background: W.AxisRectangle {
id: bg
anchors {
top: parent?.top
left: parent?.left
topMargin: root.backgroundTopMargin
leftMargin: root.backgroundLeftMargin
}
contentLayer: W.StyledRectangle.ContentLayer.Group
width: root.backgroundWidth
height: root.backgroundHeight
vertical: root.vertical
startRadius: root.getBackgroundRadius(root.startSide)
endRadius: root.getBackgroundRadius(root.endSide)
}
property Item contentItem: GridLayout {
id: layout
columns: C.Config.options.bar.vertical ? 1 : -1
anchors.fill: parent
property real spacing: 4
columnSpacing: spacing
rowSpacing: spacing
}
children: [
background,
contentItem
]
}
@@ -0,0 +1,42 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.modules.common as C
import qs.modules.common.widgets as W
W.StyledRectangle {
id: root
contentLayer: W.StyledRectangle.ContentLayer.Pane
color: C.Appearance.colors.colLayer2Base
transitions: Transition {
AnchorAnimation {
duration: C.Appearance.animation.elementMove.duration
easing.type: C.Appearance.animation.elementMove.type
easing.bezierCurve: C.Appearance.animation.elementMove.bezierCurve
}
ColorAnimation {
duration: C.Appearance.animation.elementMoveFast.duration
easing.type: C.Appearance.animation.elementMoveFast.type
easing.bezierCurve: C.Appearance.animation.elementMoveFast.bezierCurve
}
PropertyAnimation {
properties: "topLeftRadius,topRightRadius,bottomLeftRadius,bottomRightRadius,intendedWidth,intendedHeight"
duration: C.Appearance.animation.elementMove.duration
easing.type: C.Appearance.animation.elementMove.type
easing.bezierCurve: C.Appearance.animation.elementMove.bezierCurve
}
PropertyAnimation {
properties: "opacity"
duration: C.Appearance.animation.elementMoveFast.duration
easing.type: C.Appearance.animation.elementMoveFast.type
easing.bezierCurve: C.Appearance.animation.elementMoveFast.bezierCurve
}
}
W.StyledRectangularShadow {
target: root
z: -1
radius: root.topLeftRadius
}
}
@@ -0,0 +1,83 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.modules.common as C
import qs.modules.common.widgets as W
Repeater {
id: root
readonly property bool vertical: C.Config.options.bar.vertical
readonly property string invisibleItem: "_invisible"
required property list<var> componentNames
property string context: Quickshell.shellPath("modules/hefty/topLayer/bar/widgets")
model: {
const m = componentNames.map(item => {
if (item instanceof Array)
return ({"type": "container", "value": item});
else if (item === root.invisibleItem)
return ({"type": "invisible", "value": item});
else
return ({"type": "component", "value": item});
});
for (var i = 0;i < m.length; i++) {
const item = m[i];
if (item.type === "container" || item.type === "component") {
item.startSide = (i === 0) || (m[i - 1].type !== m[i].type);
item.endSide = (i + 1 >= m.length) || (m[i + 1].type !== m[i].type);
}
}
// print(JSON.stringify(m, null, 2));
return m;
}
delegate: DelegateChooser {
role: "type"
DelegateChoice {
roleValue: root.invisibleItem
delegate: Item { visible: false }
}
DelegateChoice {
roleValue: "component"
delegate: W.UserFallbackLoader {
required property var modelData
required property int index
componentName: modelData.value
context: root.context
property bool startSide: index === 0
property bool endSide: index === root.model.length - 1
Layout.fillWidth: root.vertical ? true : item.Layout.fillWidth
Layout.fillHeight: !root.vertical ? true : item.Layout.fillHeight
Layout.maximumWidth: item.Layout.maximumWidth
Layout.maximumHeight: item.Layout.maximumHeight
}
}
DelegateChoice {
roleValue: "container"
delegate: HBarGroupContainer {
id: group
required property var modelData
startSide: modelData.startSide
endSide: modelData.endSide
Repeater {
id: containerRepeater
model: group.modelData.value
delegate: W.UserFallbackLoader {
required property var modelData
required property int index
componentName: modelData
context: root.context
property bool startSide: index === 0
property bool endSide: index === group.modelData.value.length - 1
}
}
}
}
}
}
@@ -0,0 +1,9 @@
import QtQuick
import QtQuick.Layouts
import qs.modules.common as C
import qs.modules.common.widgets as W
HBarGroupContainer {
startSide: parent.startSide ?? true
endSide: parent.endSide ?? true
}
@@ -0,0 +1,73 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.modules.common as C
import qs.modules.common.functions as F
import qs.modules.common.widgets as W
import ".."
W.ButtonMouseArea {
id: root
required property bool vertical
required property bool atBottom
required property bool showPopup
readonly property var layoutParent: F.ObjectUtils.findParentWithProperty(root, "startSide")
readonly property real layoutParentTopLeftRadius: layoutParent.topLeftRadius
readonly property real layoutParentTopRightRadius: layoutParent.topRightRadius
readonly property real layoutParentBottomLeftRadius: layoutParent.bottomLeftRadius
readonly property real layoutParentBottomRightRadius: layoutParent.bottomRightRadius
readonly property real barThickness: vertical ? C.Appearance.sizes.verticalBarWidth : C.Appearance.sizes.barHeight
readonly property real barVisualThickness: vertical ? C.Appearance.sizes.baseVerticalBarWidth : C.Appearance.sizes.baseBarHeight
readonly property real barGap: (barThickness - barVisualThickness) / 2
required property real contentImplicitWidth
required property real contentImplicitHeight
property real parentRadiusToPaddingRatio: 0.3
implicitWidth: {
if (vertical) {
return barThickness;
} else {
const roundingPadding = (layoutParentTopLeftRadius + layoutParentBottomRightRadius) * parentRadiusToPaddingRatio;
return (contentImplicitWidth + roundingPadding + 4 * 2);
}
}
implicitHeight: {
if (!vertical) {
return barThickness;
} else {
const roundingPadding = (layoutParentTopLeftRadius + layoutParentBottomRightRadius) * parentRadiusToPaddingRatio;
return (contentImplicitHeight + roundingPadding + 4 * 2);
}
}
Layout.alignment: vertical ? Qt.AlignHCenter : Qt.AlignVCenter
Layout.fillWidth: vertical
Layout.fillHeight: !vertical
property alias hover: hoverOverlay.hover
property alias press: hoverOverlay.press
W.StateOverlay {
id: hoverOverlay
anchors.fill: parent
property real parentMargins: 4 + root.barGap
property real ownMargins: 2
property real edgeMargins: parentMargins + ownMargins
property real sideMargins: 2
anchors {
leftMargin: (root.vertical ? edgeMargins : sideMargins) + parentMargins * (!root.vertical && root.layoutParent.startSide)
rightMargin: (root.vertical ? edgeMargins : sideMargins) + parentMargins * (!root.vertical && root.layoutParent.endSide)
topMargin: (root.vertical ? sideMargins : edgeMargins) + parentMargins * (root.vertical && root.layoutParent.startSide)
bottomMargin: (root.vertical ? sideMargins : edgeMargins) + parentMargins * (root.vertical && root.layoutParent.endSide)
}
topLeftRadius: root.layoutParentTopLeftRadius - ownMargins
topRightRadius: root.layoutParentTopRightRadius - ownMargins
bottomLeftRadius: root.layoutParentBottomLeftRadius - ownMargins
bottomRightRadius: root.layoutParentBottomRightRadius - ownMargins
hover: root.containsMouse
press: root.containsPress
}
}
@@ -0,0 +1,260 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.modules.common as C
import qs.modules.common.widgets as W
import qs.modules.common.widgets.shapes as Shapes
import "../../../common/widgets/shapes/material-shapes.js" as MaterialShapes
// Notes
// vertical + atBottom = right side
// start radius = top or left, end radius = bottom or right
Shapes.ShapeCanvas {
id: bgShape
// Stuff fed from outside
required property bool vertical
required property bool atBottom
required property bool showPopup
required property real backgroundWidth
required property real backgroundHeight
property real popupContentWidth: 400
property real popupContentHeight: 500
property real popupPadding: 10
property real popupWidth: popupContentWidth + popupPadding * 2
property real popupHeight: popupContentHeight + popupPadding * 2
required property real startRadius
required property real endRadius
property real baseMargin: {
if (!vertical)
return parent.anchors.topMargin
else
return parent.anchors.leftMargin
}
property alias containerShape: containerShape
property alias popupShape: popupShape
// Popup constraints
// mapToGlobal is not reactive so we gotta hook manually
property real xInGlobal
property real yInGlobal
function updateXInGlobal() {
xInGlobal = mapToGlobal(0, 0).x + xOffset;
}
function updateYInGlobal() {
yInGlobal = mapToGlobal(0, 0).y + yOffset;
}
function updatePosInGlobal() {
updateXInGlobal()
updateYInGlobal()
}
Component.onCompleted: updatePosInGlobal()
onXChanged: updatePosInGlobal()
onYChanged: updatePosInGlobal()
readonly property real minPopupXOffset: -xInGlobal
readonly property real minPopupYOffset: -yInGlobal
readonly property real maxPopupXOffset: {
const maxPopupX = QsWindow.window.screen.width - popupWidth;
const maxOffset = maxPopupX - xInGlobal;
return maxOffset;
}
readonly property real maxPopupYOffset: {
const maxPopupY = QsWindow.window.screen.height - popupHeight;
const maxOffset = maxPopupY - yInGlobal;
return maxOffset;
}
property bool lockPopupX: false
property bool lockPopupY: false
readonly property real popupXOffset: {
// print("popupXOffset", popupXOffset, "lock", lockPopupX)
if (bgShape.lockPopupX) return;
if (!vertical) return Math.min(Math.max(-(popupWidth - containerShape.width) / 2, minPopupXOffset), maxPopupXOffset);
else return atBottom ? -(popupShape.width + spacing) : (containerShape.width + spacing);
if (bgShape.showPopup) lockPopupX = true;
}
onPopupXOffsetChanged: if (bgShape.showPopup) lockPopupX = true;
readonly property real popupYOffset: {
if (bgShape.lockPopupY) return;
if (!vertical) return atBottom ? -(popupShape.height + spacing) : (containerShape.height + spacing);
else return Math.min(Math.max(-(popupHeight - containerShape.height) / 2, minPopupYOffset), maxPopupYOffset)
if (bgShape.showPopup) lockPopupY = true;
}
onPopupYOffsetChanged: if (bgShape.showPopup) lockPopupY = true;
// Positioning
readonly property real popupContentOffsetBase: popupPadding
readonly property real popupContentOffsetBaseX: popupContentOffsetBase + parent.anchors.leftMargin
readonly property real popupContentOffsetBaseY: popupContentOffsetBase + parent.anchors.topMargin
readonly property real paddedContainerHeight: containerShape.height
readonly property real paddedContainerWidth: containerShape.width
readonly property real popupContentOffsetX: {
if (!vertical) return popupXOffset + popupContentOffsetBaseX;
else return paddedContainerWidth + spacing + popupContentOffsetBaseX + (atBottom * -(popupWidth + backgroundWidth + spacing * 2));
}
readonly property real popupContentOffsetY: {
if (!vertical) return paddedContainerHeight + spacing + popupContentOffsetBaseY + (atBottom * -(popupHeight + backgroundHeight + spacing * 2))
else return popupYOffset + popupContentOffsetBaseY;
}
anchors {
left: parent.left
leftMargin: {
if (!vertical) return -xOffset;
if (!bgShape.atBottom || !bgShape.showPopup) return 0;
return -popupShape.width - bgShape.spacing;
}
top: parent.top
topMargin: {
if (vertical) return -yOffset;
if (!bgShape.atBottom || !bgShape.showPopup) return 0;
return -popupShape.height - bgShape.spacing;
}
}
width: {
if (!vertical) return bgShape.showPopup ? Math.max(backgroundWidth, popupWidth) : backgroundWidth;
else return bgShape.showPopup ? (containerShape.width + popupShape.width + bgShape.spacing) : containerShape.width;
}
height: {
if (!vertical) return bgShape.showPopup ? (containerShape.height + popupShape.height + bgShape.spacing) : containerShape.height;
else return bgShape.showPopup ? Math.max(backgroundHeight, popupHeight) : backgroundHeight;
}
property bool popupHiding: false
onShowPopupChanged: popupHiding = true
onProgressChanged: {
if (progress == 1) {
popupHiding = false
if (!showPopup) {
lockPopupX = false;
lockPopupY = false;
}
}
}
color: showPopup || popupHiding ? C.Appearance.colors.colLayer3Base : C.Appearance.colors.colLayer1
xOffset: {
if (!vertical) return showPopup ? Math.max(-popupXOffset, 0) : 0;
else return bgShape.atBottom ? (width - containerShape.width) : 0;
}
yOffset: {
if (!vertical) return bgShape.atBottom ? (height - containerShape.height) : 0;
else return showPopup ? Math.max(-popupYOffset, 0) : 0;
}
animation: Anim {}
Behavior on width {
Anim {}
}
Behavior on height {
Anim {}
}
Behavior on anchors.topMargin {
animation: !bgShape.vertical ? animComp.createObject(this) : undefined
}
Behavior on anchors.leftMargin {
animation: bgShape.vertical ? animComp.createObject(this) : undefined
}
Behavior on xOffset {
animation: !bgShape.vertical ? animComp.createObject(this) : undefined
}
Behavior on yOffset {
animation: bgShape.vertical ? animComp.createObject(this) : undefined
}
polygonIsNormalized: false
property real spacing: baseMargin * 2
W.AxisRectangularContainerShape {
id: containerShape
width: bgShape.backgroundWidth
height: bgShape.backgroundHeight
startRadius: bgShape.startRadius
endRadius: bgShape.endRadius
vertical: bgShape.vertical
}
W.RectangularContainerShape {
id: popupShape
width: bgShape.popupWidth
height: bgShape.popupHeight
radius: C.Appearance.rounding.large
xOffset: bgShape.popupXOffset
yOffset: bgShape.popupYOffset
}
roundedPolygon: {
var points = [];
if (!bgShape.showPopup) return containerShape.getFullShape();
const joinRadiusOverride = containerShape.radiusLimit;
if (!bgShape.vertical) {
const popupBigger = bgShape.popupWidth > bgShape.backgroundWidth;
// Inline comment spam to mitigate qmlls' sabotaging of the (code) layout
points = [
...(bgShape.atBottom ? containerShape.getFirstBottomPoints() : [ //
...popupShape.getFirstBottomPoints(), popupShape.getBottomLeftPoint(), //
...popupShape.leftPoints, //
popupShape.getTopLeftPoint(0, -bgShape.spacing * (!popupBigger), joinRadiusOverride), //
]), //
containerShape.getBottomLeftPoint(0, bgShape.spacing * (!bgShape.atBottom && popupBigger), joinRadiusOverride), //
containerShape.getTopLeftPoint(0, -bgShape.spacing * (bgShape.atBottom && popupBigger), joinRadiusOverride), //
...(!bgShape.atBottom ? containerShape.topPoints : [ //
popupShape.getBottomLeftPoint(0, bgShape.spacing * (!popupBigger), joinRadiusOverride), //
...popupShape.leftPoints, //
popupShape.getTopLeftPoint(), //
...popupShape.topPoints, //
popupShape.getTopRightPoint(), //
...popupShape.rightPoints, //
popupShape.getBottomRightPoint(0, bgShape.spacing * (!popupBigger), joinRadiusOverride), //
]), //
containerShape.getTopRightPoint(0, -bgShape.spacing * (bgShape.atBottom && popupBigger), joinRadiusOverride), //
containerShape.getBottomRightPoint(0, bgShape.spacing * (!bgShape.atBottom && popupBigger), joinRadiusOverride), //
...(bgShape.atBottom ? containerShape.getLastBottomPoints() : [ //
popupShape.getTopRightPoint(0, -bgShape.spacing * (!popupBigger), joinRadiusOverride), //
...popupShape.rightPoints, //
popupShape.getBottomRightPoint(), //
...popupShape.getLastBottomPoints(), //
]),
];
} else {
const popupBigger = bgShape.popupHeight > bgShape.backgroundHeight;
points = [ //
...containerShape.getFirstBottomPoints(), //
containerShape.getBottomLeftPoint(-bgShape.spacing * (popupBigger && bgShape.atBottom), 0, joinRadiusOverride), //
...(!bgShape.atBottom ? containerShape.leftPoints : [ //
popupShape.getBottomRightPoint(bgShape.spacing * (!popupBigger)), //
...popupShape.bottomPoints, //
popupShape.getBottomLeftPoint(), //
...popupShape.leftPoints, //
popupShape.getTopLeftPoint(), //
...popupShape.topPoints, //
popupShape.getTopRightPoint(bgShape.spacing * (!popupBigger)), //
]), //
containerShape.getTopLeftPoint(-bgShape.spacing * (popupBigger && bgShape.atBottom), 0, joinRadiusOverride), //
...containerShape.topPoints, //
containerShape.getTopRightPoint(bgShape.spacing * (popupBigger && !bgShape.atBottom), 0, joinRadiusOverride), //
...(bgShape.atBottom ? containerShape.rightPoints : [ //
popupShape.getTopLeftPoint(-bgShape.spacing * (!popupBigger), 0, joinRadiusOverride), //
...popupShape.topPoints, //
popupShape.getTopRightPoint(), //
...popupShape.rightPoints, //
popupShape.getBottomRightPoint(), //
...popupShape.bottomPoints, //
popupShape.getBottomLeftPoint(-bgShape.spacing * (!popupBigger), 0, joinRadiusOverride), //
]), //
containerShape.getBottomRightPoint(bgShape.spacing * (popupBigger && !bgShape.atBottom), 0, joinRadiusOverride), //
...containerShape.getLastBottomPoints(), //
];
}
return MaterialShapes.customPolygon(points);
}
component Anim: NumberAnimation {
duration: C.Appearance.animation.elementMove.duration
easing.type: C.Appearance.animation.elementMove.type
easing.bezierCurve: C.Appearance.animation.elementMove.bezierCurve
}
Component {
id: animComp
Anim {}
}
}
@@ -0,0 +1,65 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.modules.common as C
import qs.modules.common.functions as F
import qs.services as S
import qs.modules.common.widgets as W
import ".."
HBarWidgetContainer {
id: root
property bool showPopup: false
readonly property bool vertical: C.Config.options.bar.vertical
readonly property bool atBottom: C.Config.options.bar.bottom
// Interactions
property var morphedPanelParent: F.ObjectUtils.findParentWithProperty(root, "maskItems")
onShowPopupChanged: {
if (root.showPopup) {
root.morphedPanelParent.addAttachedMaskItem(bgShape);
} else {
root.morphedPanelParent.removeAttachedMaskItem(bgShape);
}
}
Connections {
target: root.morphedPanelParent
function onFocusGrabDismissed() {
root.showPopup = false;
}
}
// Background container shape
property alias backgroundShape: bgShape
property alias popupContentWidth: bgShape.popupContentWidth
property alias popupContentHeight: bgShape.popupContentHeight
property alias popupContentOffsetX: bgShape.popupContentOffsetX
property alias popupContentOffsetY: bgShape.popupContentOffsetY
background: Item {
anchors {
top: parent.top
left: parent.left
topMargin: root.backgroundTopMargin
leftMargin: root.backgroundLeftMargin
}
implicitWidth: root.backgroundWidth
implicitHeight: root.backgroundHeight
HBarWidgetShapeBackground {
id: bgShape
vertical: root.vertical
atBottom: root.atBottom
showPopup: root.showPopup
backgroundWidth: root.backgroundWidth
backgroundHeight: root.backgroundHeight
startRadius: root.getBackgroundRadius(root.startSide)
endRadius: root.getBackgroundRadius(root.endSide)
}
}
}
@@ -0,0 +1,79 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import ".."
HBarWidgetWithPopout {
id: root
property bool showPing: false
Connections {
target: Ai
function onResponseFinished() {
if (GlobalStates.sidebarLeftOpen) return;
root.showPing = true;
}
}
Connections {
target: Booru
function onResponseFinished() {
if (GlobalStates.sidebarLeftOpen) return;
root.showPing = true;
}
}
Connections {
target: GlobalStates
function onSidebarLeftOpenChanged() {
root.showPing = false;
}
}
HBarWidgetContent {
id: contentRoot
parentRadiusToPaddingRatio: 0
vertical: root.vertical
atBottom: root.atBottom
contentImplicitWidth: 14
contentImplicitHeight: 14
implicitWidth: 40
implicitHeight: 46
showPopup: false
onClicked: GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen;
CustomIcon {
id: distroIcon
anchors.centerIn: parent
width: 20
height: 20
source: Config.options.bar.topLeftIcon == 'distro' ? SystemInfo.distroIcon : `${Config.options.bar.topLeftIcon}-symbolic`
colorize: true
color: Appearance.colors.colOnLayer0
Rectangle {
opacity: root.showPing ? 1 : 0
visible: opacity > 0
anchors {
bottom: parent.bottom
right: parent.right
bottomMargin: -2
rightMargin: -2
}
implicitWidth: 8
implicitHeight: 8
radius: Appearance.rounding.full
color: Appearance.colors.colTertiary
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
}
}
}
@@ -0,0 +1,27 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs
import qs.modules.common.widgets
import ".."
HBarWidgetWithPopout {
id: root
HBarWidgetContent {
id: contentRoot
vertical: root.vertical
atBottom: root.atBottom
contentImplicitWidth: symbol.implicitWidth
contentImplicitHeight: symbol.implicitHeight
showPopup: false
onClicked: GlobalStates.sessionOpen = true;
MaterialSymbol {
id: symbol
anchors.centerIn: parent
iconSize: 20
text: "power_settings_new"
}
}
}
@@ -0,0 +1,598 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Services.UPower
import qs.services as S
import qs.modules.common as C
import qs.modules.common.functions as F
import qs.modules.common.widgets as W
import ".."
HBarWidgetWithPopout {
id: root
property bool chargingAndNotFull: S.Battery.isCharging && S.Battery.percentage < 1
property bool powerSaving: PowerProfiles.profile == PowerProfile.PowerSaver
popupContentWidth: popupContent.implicitWidth
popupContentHeight: popupContent.implicitHeight
HBarWidgetContent {
id: contentRoot
vertical: root.vertical
atBottom: root.atBottom
showPopup: root.showPopup
onClicked: root.showPopup = !root.showPopup
contentImplicitWidth: sysInfoContent.implicitWidth
contentImplicitHeight: sysInfoContent.implicitHeight
parentRadiusToPaddingRatio: 0
SysInfoContent {
id: sysInfoContent
anchors.fill: parent
}
SysInfoPopupContent {
id: popupContent
anchors {
top: parent.top
left: parent.left
topMargin: root.popupContentOffsetY
leftMargin: root.popupContentOffsetX
}
shown: root.showPopup
}
}
component SysInfoContent: Item {
implicitWidth: contentGrid.implicitWidth + contentGrid.anchors.leftMargin + contentGrid.anchors.rightMargin
implicitHeight: contentGrid.implicitHeight + contentGrid.anchors.topMargin + contentGrid.anchors.bottomMargin
W.BoxLayout {
id: contentGrid
vertical: root.vertical
property int visibleChildren: children.filter(child => child.shown).length
property bool hasResourceIndicators: visibleChildren > 1 || (visibleChildren > 0 && !S.Battery.available)
anchors {
fill: parent
leftMargin: !root.vertical ? (hasResourceIndicators ? 1 : 4) : 0
topMargin: root.vertical ? 2 : 0
rightMargin: !root.vertical ? (root.endSide ? 1 : (battLoader.visible ? 0 : -3)) : 0
bottomMargin: root.vertical ? (root.endSide ? 4 : 2) : 0
}
spacing: 4
Item {} // Trick for extra spacing
Item {
visible: root.startSide
}
AlignedFadeLoader {
shown: C.Config.options.hefty.bar.resources.showMemory || (
!battLoader.visible
&& !C.Config.options.hefty.bar.resources.showRam
&& !C.Config.options.hefty.bar.resources.showSwap
&& !C.Config.options.hefty.bar.resources.showCpu
)
sourceComponent: Memory {}
}
AlignedFadeLoader {
shown: C.Config.options.hefty.bar.resources.showRam
sourceComponent: RamOnly {}
}
AlignedFadeLoader {
shown: C.Config.options.hefty.bar.resources.showSwap
sourceComponent: SwapOnly {}
}
AlignedFadeLoader {
shown: C.Config.options.hefty.bar.resources.showCpu
sourceComponent: Cpu {}
}
AlignedFadeLoader {
id: battLoader
shown: S.Battery.available
Layout.fillWidth: root.vertical
Layout.fillHeight: !root.vertical
sourceComponent: Battery {}
}
Item {}
}
}
component AlignedFadeLoader: W.FadeLoader {
Layout.alignment: root.vertical ? Qt.AlignHCenter : Qt.AlignVCenter
}
component Memory: SysResourceProgress {
valueWeights: [S.ResourceUsage.memoryTotal, S.ResourceUsage.swapTotal]
values: [S.ResourceUsage.memoryUsedPercentage, S.ResourceUsage.swapUsedPercentage]
centerChar: S.Translation.tr("Memory")[0]
}
component RamOnly: SysResourceProgress {
values: [S.ResourceUsage.memoryUsedPercentage]
centerChar: S.Translation.tr("RAM")[0]
}
component SwapOnly: SysResourceProgress {
values: [S.ResourceUsage.swapUsedPercentage]
centerChar: S.Translation.tr("Swap")[0]
}
component Cpu: SysResourceProgress {
values: [S.ResourceUsage.cpuUsage]
centerChar: S.Translation.tr("CPU")[0]
}
component SysResourceProgress: W.CombinedCircularProgress {
id: sysResProg
implicitSize: 22
valueHighlights: [C.Appearance.colors.colPrimary]
valueTroughs: [C.Appearance.colors.colSecondaryContainer]
property string centerChar: ""
W.StyledText {
renderType: Text.QtRendering
anchors.centerIn: parent
text: sysResProg.centerChar
font.pixelSize: 9
}
}
component Battery: Item {
implicitWidth: !root.vertical ? battShape.implicitWidth : battShape.implicitHeight
implicitHeight: !root.vertical ? battShape.implicitHeight : battShape.implicitWidth
BatteryShape {
id: battShape
anchors.centerIn: parent
}
}
component BatteryShape: Row {
Layout.fillHeight: true
spacing: 1.5
rotation: -90 * root.vertical
W.ClippedProgressBar {
id: batteryProgress
anchors.verticalCenter: parent.verticalCenter
valueBarWidth: 28
valueBarHeight: 16
radius: 4
progressRadius: 0
value: S.Battery.percentage
highlightColor: (S.Battery.isLow && !S.Battery.isCharging) ? C.Appearance.m3colors.m3error : C.Appearance.colors.colOnSecondaryContainer
font.pixelSize: boltIcon.visible ? 13 : 14
Item {
layer.enabled: true
width: batteryProgress.valueBarWidth
height: batteryProgress.valueBarHeight
RowLayout {
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: (parent.height - height) / 2
}
rotation: 180 * root.vertical
spacing: 0
W.MaterialSymbol {
id: boltIcon
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: -2
Layout.rightMargin: -2
rotation: -90 * root.vertical
fill: 1 * (text == "bolt")
fillAnimation: null
text: {
if (batteryProgress.value == 1)
return "check";
if (root.chargingAndNotFull)
return "bolt";
if (root.powerSaving)
return "nest_eco_leaf";
return "";
}
iconSize: C.Appearance.font.pixelSize.small
font.weight: Font.DemiBold
visible: text != ""
}
W.VisuallyCenteredStyledText {
visible: batteryProgress.value < 1
Layout.fillHeight: true
rotation: -90 * root.vertical
font: batteryProgress.font
text: batteryProgress.text
}
}
}
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
color: batteryProgress.trackColor
topRightRadius: width
bottomRightRadius: width
implicitWidth: 2.5
implicitHeight: 8
}
}
component SysInfoPopupContent: W.ChoreographerLoader {
id: sysInfoPopupContent
sourceComponent: W.ChoreographerGridLayout {
id: popupRoot
rowSpacing: 10
property bool showSettings: false
onShownChanged: {
if (shown) {
powerProfileSelection.focusSelectedChild();
}
}
W.FlyFadeEnterChoreographable {
z: 1
Layout.fillWidth: true
Item {
anchors {
left: parent.left
right: parent.right
}
implicitHeight: popupHeaderLayout.implicitHeight
ColumnLayout {
anchors {
top: parent.top
left: parent.left
right: parent.right
}
spacing: 10
RowLayout {
id: popupHeaderLayout
Layout.fillWidth: true
W.StyledText {
Layout.fillWidth: true
text: S.Translation.tr("Resources")
elide: Text.ElideRight
font.pixelSize: C.Appearance.font.pixelSize.title
}
W.StyledIconButton {
implicitSize: C.Appearance.rounding.normal * 2
text: "settings"
iconSize: 20
onClicked: popupRoot.showSettings = !popupRoot.showSettings;
checked: popupRoot.showSettings
}
}
W.FadeLoader {
shown: popupRoot.showSettings
Layout.fillWidth: true
sourceComponent: SysInfoPopupConfig {}
}
}
}
}
W.FlyFadeEnterChoreographable {
Layout.fillWidth: true
Column {
anchors {
left: parent.left
right: parent.right
}
spacing: 2
Item {
anchors {
left: parent.left
right: parent.right
}
implicitHeight: memUsed.implicitHeight
BigSmallTextPair {
id: memUsed
materialSymbol: "memory"
bigText: S.ResourceUsage.kbToGbString(S.ResourceUsage.memoryUsed, false)
smallText: {
const total = S.ResourceUsage.kbToGbString(S.ResourceUsage.memoryTotal, false);
return S.Translation.tr("%1").arg(`/ ${total}`)
}
W.StyledText {
Layout.alignment: Qt.AlignBaseline
text: S.Translation.tr("Memory")
color: C.Appearance.colors.colOutline
}
}
BigSmallTextPair {
id: swapUsed
TextMetrics {
id: plusTextMetric
font: swapUsed.bigFont
text: "+"
}
property real halfWidthOfAPlus: plusTextMetric.width / 2
x: Math.min(memProg.availableWidth * memProg.visualEnds[0] - halfWidthOfAPlus, parent.width - width)
bigText: "+ " + S.ResourceUsage.kbToGbString(S.ResourceUsage.swapUsed, false)
smallText: {
const total = S.ResourceUsage.kbToGbString(S.ResourceUsage.swapTotal, false);
return `/ ${total} GB`
}
}
}
W.StyledCombinedProgressBar {
id: memProg
anchors {
left: parent.left
right: parent.right
}
valueWeights: [S.ResourceUsage.memoryTotal, S.ResourceUsage.swapTotal]
values: [S.ResourceUsage.memoryUsedPercentage, S.ResourceUsage.swapUsedPercentage]
}
}
}
W.FlyFadeEnterChoreographable {
Layout.fillWidth: true
Column {
anchors {
left: parent.left
right: parent.right
}
spacing: 2
BigSmallTextPair {
spacing: 0
materialSymbol: "developer_board"
bigText: Math.round(S.ResourceUsage.cpuUsage * 100)
smallText: "%"
W.StyledText {
Layout.alignment: Qt.AlignBaseline
text: " " + S.Translation.tr("CPU")
color: C.Appearance.colors.colOutline
}
}
W.StyledCombinedProgressBar {
anchors {
left: parent.left
right: parent.right
}
property bool useSingleAggregate: S.ResourceUsage.cpuCoreUsages.length > 8
valueWeights: useSingleAggregate ? [1] : S.ResourceUsage.cpuCoreFreqCaps
values: useSingleAggregate ? [S.ResourceUsage.cpuUsage] : S.ResourceUsage.cpuCoreUsages
}
}
}
W.FlyFadeEnterChoreographable {
Layout.topMargin: 8
Layout.fillWidth: true
RowLayout {
spacing: 10
width: parent.width
W.CircularProgress {
id: battCircProg
implicitSize: 44
lineWidth: 3
value: S.Battery.percentage
W.MaterialSymbol {
anchors.centerIn: parent
iconSize: 22
fill: 1
animateChange: true
text: {
if (battCircProg.value == 1)
return "check";
if (root.chargingAndNotFull)
return "bolt";
if (root.powerSaving)
return "energy_savings_leaf";
if (PowerProfiles.profile == PowerProfile.Performance)
return "local_fire_department";
return C.Icons.getBatteryIcon(battCircProg.value * 100);
}
}
}
RowLayout {
Layout.fillWidth: false
spacing: 4
W.StyledText {
Layout.alignment: Qt.AlignBaseline
visible: S.Battery.knownEnergyRate
text: F.DateUtils.formatDuration(S.Battery.isCharging ? S.Battery.timeToFull : S.Battery.timeToEmpty)
font.pixelSize: C.Appearance.font.pixelSize.title
}
W.StyledText {
Layout.alignment: Qt.AlignBaseline
text: {
if (!S.Battery.knownEnergyRate)
return S.Battery.isCharging ? S.Translation.tr("Charging") : S.Translation.tr("Discharging");
return S.Battery.isCharging ? S.Translation.tr("to full") : S.Translation.tr("remaining");
}
}
}
Item {
Layout.fillWidth: true
}
ColumnLayout {
id: notSoImportantBatteryStats
Layout.fillWidth: false
spacing: 4
StatWithIcon {
visible: S.Battery.knownEnergyRate
Layout.alignment: Qt.AlignLeft
icon: "bolt"
value: `${S.Battery.energyRate.toFixed(1)}W`
longestValueString: "69.0W"
}
StatWithIcon {
Layout.alignment: Qt.AlignLeft
icon: "favorite"
value: `${(S.Battery.health).toFixed(1 * (S.Battery.health < 100))}%`
longestValueString: "69.0%"
}
}
}
}
W.FlyFadeEnterChoreographable {
Layout.fillWidth: true
W.ConfigSelectionArray {
id: powerProfileSelection
currentValue: PowerProfiles.profile
onSelected: newValue => {
PowerProfiles.profile = newValue;
}
options: [
{
displayName: S.Translation.tr("Power saver"),
value: PowerProfile.PowerSaver
},
{
displayName: S.Translation.tr("Balanced"),
value: PowerProfile.Balanced
},
{
displayName: S.Translation.tr("Performance"),
value: PowerProfile.Performance
}
]
}
}
}
}
component SysInfoPopupConfig: Rectangle {
implicitWidth: sysConfLayout.implicitWidth
implicitHeight: sysConfLayout.implicitHeight
radius: C.Appearance.rounding.normal
color: C.Appearance.colors.colLayer4Base
W.StyledRectangularShadow {
target: parent
z: -1
}
ColumnLayout {
id: sysConfLayout
anchors.fill: parent
W.ConfigSwitch {
buttonIcon: "pie_chart"
text: S.Translation.tr("Show memory usage")
checked: C.Config.options.hefty.bar.resources.showMemory
onCheckedChanged: {
C.Config.options.hefty.bar.resources.showMemory = checked;
}
}
W.ConfigSwitch {
buttonIcon: "memory"
text: S.Translation.tr("Show RAM usage")
checked: C.Config.options.hefty.bar.resources.showRam
onCheckedChanged: {
C.Config.options.hefty.bar.resources.showRam = checked;
}
}
W.ConfigSwitch {
buttonIcon: "swap_horiz"
text: S.Translation.tr("Show swap")
checked: C.Config.options.hefty.bar.resources.showSwap
onCheckedChanged: {
C.Config.options.hefty.bar.resources.showSwap = checked;
}
}
W.ConfigSwitch {
buttonIcon: "planner_review"
text: S.Translation.tr("Show CPU usage")
checked: C.Config.options.hefty.bar.resources.showCpu
onCheckedChanged: {
C.Config.options.hefty.bar.resources.showCpu = checked;
}
}
}
}
component BigSmallTextPair: RowLayout {
id: txtPair
property string materialSymbol: ""
property string bigText: ""
property string smallText: ""
property alias bigFont: bigTxt.font
property alias smallFont: smallTxt.font
spacing: 6
W.MaterialSymbol {
Layout.rightMargin: 6 - spacing
visible: text.length > 0
Layout.alignment: Qt.AlignVCenter
text: txtPair.materialSymbol
fill: 1
iconSize: 24
}
W.StyledText {
id: bigTxt
Layout.alignment: Qt.AlignBaseline
font.pixelSize: C.Appearance.font.pixelSize.title
text: txtPair.bigText
}
W.StyledText {
id: smallTxt
Layout.alignment: Qt.AlignBaseline
text: txtPair.smallText
}
}
component StatWithIcon: Item {
id: statItem
required property string icon
required property string value
property string longestValueString
implicitWidth: statRow.implicitWidth
implicitHeight: statRow.implicitHeight
RowLayout {
id: statRow
anchors.fill: parent
spacing: 4
W.MaterialSymbol {
Layout.fillWidth: false
Layout.alignment: Qt.AlignVCenter
text: statItem.icon
fill: 1
iconSize: 16
}
W.FixedWidthTextContainer {
longestText: statItem.longestValueString
W.VisuallyCenteredStyledText {
anchors.fill: parent
horizontalAlignment: Text.AlignLeft
text: statItem.value
}
}
Item {
Layout.fillWidth: true
}
}
}
}
@@ -0,0 +1,134 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Services.UPower
import qs
import qs.services as S
import qs.modules.common as C
import qs.modules.common.functions as F
import qs.modules.common.widgets as W
import ".."
HBarWidgetWithPopout {
id: root
property bool chargingAndNotFull: S.Battery.isCharging && S.Battery.percentage < 1
property bool powerSaving: PowerProfiles.profile == PowerProfile.PowerSaver
popupContentWidth: popupContent.implicitWidth
popupContentHeight: popupContent.implicitHeight
HBarWidgetContent {
id: contentRoot
vertical: root.vertical
atBottom: root.atBottom
showPopup: root.showPopup
contentImplicitWidth: activeItem.implicitWidth
contentImplicitHeight: activeItem.implicitHeight
onClicked: GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen; // TODO: use own popup
property var activeItem: content
SystemIndicatorsContent {
id: content
anchors {
top: !root.vertical ? parent.top : undefined
bottom: !root.vertical ? parent.bottom : undefined
left: root.vertical ? parent.left : undefined
right: root.vertical ? parent.right : undefined
horizontalCenter: !root.vertical ? parent.horizontalCenter : undefined
verticalCenter: root.vertical ? parent.verticalCenter : undefined
}
}
SystemPanel {
id: popupContent
anchors {
top: root.vertical ? contentRoot.activeItem.top : contentRoot.activeItem.top
topMargin: root.popupContentOffsetY
left: root.vertical ? contentRoot.activeItem.left : contentRoot.activeItem.left
leftMargin: root.popupContentOffsetX
}
shown: root.showPopup
}
}
component SystemIndicatorsContent: W.BoxLayout {
vertical: root.vertical
rowSpacing: 2
columnSpacing: 8
VolMuteIcon {}
MicMuteIcon {}
WifiIcon {}
BluetoothIcon {}
}
component VolMuteIcon: IconIndicator {
visible: S.Audio.sink?.audio?.muted ?? false
W.MaterialSymbol {
anchors.centerIn: parent
text: "volume_off"
iconSize: 20
}
}
component MicMuteIcon: IconIndicator {
visible: S.Audio.source?.audio?.muted ?? false
W.MaterialSymbol {
anchors.centerIn: parent
text: "mic_off"
iconSize: 20
}
}
component BluetoothIcon: IconIndicator {
visible: S.BluetoothStatus.available
child: W.MaterialSymbol {
anchors.centerIn: parent
iconSize: 20
text: S.BluetoothStatus.connected ? "bluetooth_connected" : S.BluetoothStatus.enabled ? "bluetooth" : "bluetooth_disabled"
}
}
component WifiIcon: IconIndicator {
child: W.MaterialSymbol {
anchors.centerIn: parent
text: C.Icons.getNetworkMaterialSymbol()
iconSize: 20
}
}
component IconIndicator: Item {
Layout.fillWidth: root.vertical
Layout.fillHeight: !root.vertical
default property Item child
implicitWidth: child.implicitWidth
implicitHeight: child.implicitHeight
children: [child]
}
component SystemPanel: W.ChoreographerLoader {
sourceComponent: W.ChoreographerGridLayout {
id: panelRoot
W.FlyFadeEnterChoreographable {
W.StyledText {
text: "five little chuddies jumping on da bed"
}
}
W.FlyFadeEnterChoreographable {
W.StyledText {
text: "one fell off and bumped his head"
}
}
W.FlyFadeEnterChoreographable {
W.StyledText {
text: "momma call the doctor and the doctor sez"
}
}
}
}
}
@@ -0,0 +1,140 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.ii.bar
import ".."
HBarWidgetWithPopout {
id: root
property list<var> pinnedItems: TrayService.pinnedItems
property list<var> unpinnedItems: TrayService.unpinnedItems
popupContentWidth: popupContent.implicitWidth
popupContentHeight: popupContent.implicitHeight
Layout.maximumWidth: vertical ? -1 : implicitWidth
Layout.maximumHeight: vertical ? implicitHeight : -1
Layout.fillWidth: true
Layout.fillHeight: true
HBarWidgetContent {
id: contentRoot
vertical: root.vertical
atBottom: root.atBottom
showPopup: root.showPopup
contentImplicitWidth: trayContent.implicitWidth
contentImplicitHeight: trayContent.implicitHeight
hoverEnabled: false
parentRadiusToPaddingRatio: 0.45
hover: trayContent.moreHovered
press: trayContent.morePressed
TrayContent {
id: trayContent
anchors.fill: parent
vertical: root.vertical
}
UnpinnedItemsPopup {
id: popupContent
anchors {
top: parent.top
topMargin: root.popupContentOffsetY
left: parent.left
leftMargin: root.popupContentOffsetX
}
shown: root.showPopup
}
}
component TrayContent: BoxLayout {
spacing: 4
property alias moreHovered: moreBtn.containsMouse
property alias morePressed: moreBtn.containsPress
ButtonMouseArea {
id: moreBtn
visible: TrayService.unpinnedItems.length > 0
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: 4 * root.vertical
Layout.leftMargin: 4 * !root.vertical
hoverEnabled: true
acceptedButtons: Qt.AllButtons
implicitWidth: 20 - parent.spacing
implicitHeight: 20 - parent.spacing
onClicked: root.showPopup = !root.showPopup
MaterialSymbol {
anchors.centerIn: parent
iconSize: 20
text: "expand_more"
horizontalAlignment: Text.AlignHCenter
color: root.showPopup ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer2
rotation: (root.showPopup ? 180 : 0) - (90 * root.vertical) + (180 * root.atBottom)
Behavior on rotation {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
}
Repeater {
model: root.pinnedItems
delegate: StyledTrayItem {
Layout.fillWidth: true
Layout.fillHeight: true
required property var modelData
item: modelData
}
}
}
component UnpinnedItemsPopup: ChoreographerLoader {
sourceComponent: ChoreographerGridLayout {
id: popupRoot
totalDuration: 70
columns: root.vertical ? 1 : -1
columnSpacing: 8
rowSpacing: 8
Repeater {
model: root.unpinnedItems
delegate: FlyFadeEnterChoreographable {
id: unpinnedTrayItem
required property var modelData
StyledTrayItem {
item: unpinnedTrayItem.modelData
}
}
}
}
}
component StyledTrayItem: SysTrayItem {
iconSize: 18
propagateComposedEvents: false
property var menuWindow
onMenuClosed: {
if (menuWindow)
GlobalFocusGrab.removeDismissable(menuWindow);
}
onMenuOpened: (qsWindow) => {
menuWindow = qsWindow;
GlobalFocusGrab.addDismissable(qsWindow)
}
}
}
@@ -0,0 +1,283 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.modules.common as C
import qs.services as S
import qs.modules.common.widgets as W
import ".."
HBarWidgetWithPopout {
id: root
readonly property string timeFormatString: C.Config.options.time.format
readonly property bool is12h: timeFormatString.startsWith("h:")
readonly property bool hasAmPm: timeFormatString.toLowerCase().includes("ap") || timeFormatString.toLowerCase().endsWith("a")
readonly property bool capitalizedAmPm: timeFormatString.includes("AP") || timeFormatString.endsWith("A")
popupContentWidth: popupContent.implicitWidth
popupContentHeight: popupContent.implicitHeight
// The button on the bar
HBarWidgetContent {
id: contentRoot
vertical: root.vertical
atBottom: root.atBottom
showPopup: root.showPopup
onClicked: root.showPopup = !showPopup
contentImplicitWidth: activeItem.implicitWidth
contentImplicitHeight: activeItem.implicitHeight
property Item activeItem: vertical ? verticalContent : horizontalContent
// When horizontal
Loader {
id: horizontalContent
anchors.fill: parent
active: !contentRoot.vertical
sourceComponent: HorizontalClock {}
}
// When vertical
Loader {
id: verticalContent
anchors.fill: parent
active: contentRoot.vertical
sourceComponent: VerticalClock {}
}
// Popup content
PopupContent {
id: popupContent
anchors {
top: parent.top
left: parent.left
topMargin: root.popupContentOffsetY
leftMargin: root.popupContentOffsetX
}
shown: root.showPopup
}
}
component HorizontalClock: Item {
implicitWidth: contentLayout.implicitWidth
implicitHeight: contentLayout.implicitHeight
RowLayout {
id: contentLayout
anchors.fill: parent
W.FixedWidthTextContainer {
Layout.leftMargin: contentRoot.layoutParentTopLeftRadius * contentRoot.parentRadiusToPaddingRatio
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.fillHeight: true
longestText: Qt.locale().toString(new Date(1984, 6, 9, 00, 00, 00), root.timeFormatString)
font: clockText.font
W.VisuallyCenteredStyledText {
id: clockText
anchors.fill: parent
font.pixelSize: C.Appearance.font.pixelSize.large
color: C.Appearance.colors.colOnLayer1
text: S.DateTime.time
}
}
W.VisuallyCenteredStyledText {
Layout.alignment: Qt.AlignVCenter
font.pixelSize: C.Appearance.font.pixelSize.small
color: C.Appearance.colors.colOnLayer1
text: "•"
}
W.VisuallyCenteredStyledText {
Layout.alignment: Qt.AlignVCenter
font.pixelSize: C.Appearance.font.pixelSize.small
color: C.Appearance.colors.colOnLayer1
text: S.DateTime.longDate
}
}
}
component VerticalClock: Item {
implicitWidth: contentLayoutVertical.implicitWidth
implicitHeight: contentLayoutVertical.implicitHeight
ColumnLayout {
id: contentLayoutVertical
anchors.fill: parent
spacing: amPmText.visible ? -2 : -4
ColumnLayout {
id: verticalTime
Layout.alignment: Qt.AlignHCenter
spacing: -4
W.StyledText {
Layout.alignment: Qt.AlignHCenter
font.pixelSize: C.Appearance.font.pixelSize.large
color: C.Appearance.colors.colOnLayer1
text: {
var hrs = S.DateTime.clock.hours;
if (root.is12h && hrs != 12)
hrs %= 12;
return hrs.toString().padStart(2, '0');
}
}
W.StyledText {
Layout.alignment: Qt.AlignHCenter
font.pixelSize: C.Appearance.font.pixelSize.large
color: C.Appearance.colors.colOnLayer1
text: S.DateTime.clock.minutes.toString().padStart(2, '0')
}
W.StyledText {
id: amPmText
visible: root.hasAmPm
Layout.topMargin: -2
Layout.alignment: Qt.AlignHCenter
font.pixelSize: C.Appearance.font.pixelSize.smaller
color: C.Appearance.colors.colOnLayer1
text: Qt.locale().toString(S.DateTime.clock.date, root.capitalizedAmPm ? "AP" : "ap")
}
}
W.StyledText {
Layout.alignment: Qt.AlignHCenter
font.pixelSize: C.Appearance.font.pixelSize.smallest
color: C.Appearance.colors.colOnLayer1
text: S.DateTime.shortDate
}
}
}
component PopupContent: W.ChoreographerLoader {
sourceComponent: W.ChoreographerGridLayout {
id: popupRoot
property real buttonSize: C.Appearance.rounding.normal * 2
property real buttonSpacing: 4
rowSpacing: 2
W.FlyFadeEnterChoreographable {
Layout.fillWidth: true
Layout.bottomMargin: 6
RowLayout {
width: parent.width
spacing: 0
W.StyledText {
Layout.leftMargin: 6
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
text: calendarView.title
font.pixelSize: C.Appearance.font.pixelSize.larger
elide: Text.ElideRight
color: C.Appearance.colors.colSecondary
}
W.StyledIconButton {
implicitSize: 30
text: "chevron_left"
iconSize: 20
onClicked: calendarView.scrollMonthsAndSnap(-1)
colForeground: C.Appearance.colors.colPrimary
}
W.StyledIconButton {
implicitSize: 30
text: "chevron_right"
iconSize: 20
onClicked: calendarView.scrollMonthsAndSnap(1)
colForeground: C.Appearance.colors.colPrimary
}
W.StyledIconButton {
implicitSize: 30
text: "rotate_left"
iconSize: 20
onClicked: calendarView.scrollToToday()
colForeground: C.Appearance.colors.colPrimary
enabled: calendarView.targetWeekDiff != 0
}
}
}
W.FlyFadeEnterChoreographable {
Layout.alignment: Qt.AlignHCenter
W.CalendarDaysOfWeek {
locale: calendarView.locale
spacing: popupRoot.buttonSpacing
delegate: Item {
id: dowItem
required property var model
implicitWidth: popupRoot.buttonSize
implicitHeight: dowText.implicitHeight
W.VisuallyCenteredStyledText {
id: dowText
anchors.centerIn: parent
font.pixelSize: C.Appearance.font.pixelSize.smaller
color: C.Appearance.colors.colOutline
text: {
var result = dowItem.model.shortName;
if (C.Config.options.calendar.force2CharDayOfWeek)
result = result.substring(0, 2);
return result;
}
}
}
}
}
W.FlyFadeEnterChoreographable {
Item {
implicitWidth: calendarView.implicitWidth - calendarView.horizontalPadding * 2
implicitHeight: calendarView.implicitHeight - calendarView.verticalPadding * 2
W.CalendarView {
id: calendarView
anchors.centerIn: parent
locale: Qt.locale(C.Config.options.calendar.locale)
verticalPadding: 4
horizontalPadding: 4
buttonSize: popupRoot.buttonSize
buttonSpacing: popupRoot.buttonSpacing
buttonVerticalSpacing: popupRoot.buttonSpacing
Layout.fillWidth: true
delegate: W.StyledButton {
id: dayButton
required property var model
focus: model.today
checked: model.today
enabled: model.month === calendarView.focusedMonth
implicitWidth: popupRoot.buttonSize
implicitHeight: popupRoot.buttonSize
width: implicitWidth
height: implicitHeight
text: model.day
Connections {
target: popupRoot
enabled: dayButton.model.today
function onShownChanged() {
if (popupRoot.shown)
dayButton.forceActiveFocus();
}
}
contentItem: Item {
W.VisuallyCenteredStyledText {
anchors.centerIn: parent
text: dayButton.text
color: dayButton.colForeground
}
}
}
}
}
}
}
}
}
@@ -0,0 +1,282 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import ".."
HBarWidgetWithPopout {
id: root
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
readonly property var activeHyprlandClient: HyprlandData.clientForToplevel(activeWindow)
readonly property bool focusingThisMonitor: HyprlandData.activeWorkspace?.monitor == monitor?.name
readonly property var biggestWindow: HyprlandData.biggestWindowForWorkspace(HyprlandData.monitors[root.monitor?.id]?.activeWorkspace.id)
readonly property bool hasFocusedWindow: (focusingThisMonitor && activeWindow?.activated && biggestWindow) ?? false
readonly property string primaryText: {
if (root.hasFocusedWindow)
return root.activeWindow?.title;
return (root.biggestWindow?.title) ?? `${Translation.tr("Workspace")} ${root.monitor?.activeWorkspace?.id ?? 1}`;
}
readonly property string secondaryText: {
if (root.hasFocusedWindow && root.activeWindow?.appId != "" && root.activeWindow?.appId != primaryText)
return root.activeWindow?.appId;
return Translation.tr("Options")
}
onPrimaryTextChanged: showPopup = false;
property real fontPixelSize: Appearance.font.pixelSize.smaller
Layout.maximumWidth: vertical ? -1 : implicitWidth
Layout.fillWidth: true
popupContentWidth: popupContent.implicitWidth
popupContentHeight: popupContent.implicitHeight
HBarWidgetContent {
id: contentRoot
Layout.fillWidth: true
Layout.fillHeight: true
vertical: root.vertical
atBottom: root.atBottom
contentImplicitWidth: winTitleContent.implicitWidth
contentImplicitHeight: winTitleContent.implicitHeight
showPopup: false
onClicked: root.showPopup = !root.showPopup;
WinTitleContent {
id: winTitleContent
}
WinOptionsPopup {
id: popupContent
anchors {
top: parent.top
topMargin: root.popupContentOffsetY
left: parent.left
leftMargin: root.popupContentOffsetX
}
shown: root.showPopup
}
}
component WinTitleContent: BoxLayout {
anchors.fill: parent
vertical: root.vertical
spacing: 4
Item {
Layout.leftMargin: 4 * !root.vertical
Layout.topMargin: 3 * root.vertical
Layout.bottomMargin: 4 * root.vertical
Layout.alignment: Qt.AlignCenter
implicitWidth: appIcon.implicitWidth
implicitHeight: appIcon.implicitHeight
AppIcon {
id: appIcon
anchors.centerIn: parent
opacity: 0
source: Quickshell.iconPath(AppSearch.guessIcon(root.activeWindow?.appId), "image-missing")
implicitSize: 16
animated: false
}
Circle {
id: iconMask
visible: false
layer.enabled: true
diameter: appIcon.implicitSize
}
Loader {
id: renderedIconLoader
anchors.fill: appIcon
visible: root.activeWindow
sourceComponent: Colorizer {
implicitWidth: appIcon.implicitWidth
implicitHeight: appIcon.implicitHeight
colorizationColor: Appearance.m3colors.darkmode ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary
colorization: Config.options.bar.workspaces.monochromeIcons ? 0.8 : 0.5
brightness: 0
source: appIcon
maskEnabled: true
maskSource: iconMask
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
}
MaterialSymbol {
anchors.centerIn: parent
visible: !renderedIconLoader.visible
text: "overview_key"
iconSize: 16
}
}
Item {
visible: !root.vertical
Layout.rightMargin: 4
Layout.alignment: Qt.AlignCenter
Layout.fillHeight: true
// No overflow
Layout.maximumWidth: implicitWidth
Layout.fillWidth: true
// Size
implicitWidth: winText.implicitWidth
implicitHeight: winText.implicitHeight
FlyFadeEnterChoreographable {
anchors.fill: parent
progress: contentRoot.containsMouse ? 0 : 1
reverseDirection: true
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
VisuallyCenteredStyledText {
id: winText
height: parent.height
width: parent.width
elide: Text.ElideRight
// Styles & text
font.pixelSize: root.fontPixelSize
text: root.primaryText
}
}
FlyFadeEnterChoreographable {
anchors.fill: parent
progress: contentRoot.containsMouse ? 1 : 0
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
VisuallyCenteredStyledText {
height: parent.height
width: parent.width
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
// Styles & text
font.pixelSize: root.fontPixelSize
text: root.secondaryText
}
}
}
}
component WinOptionsPopup: ChoreographerLoader {
sourceComponent: ChoreographerGridLayout {
id: popupRoot
columns: 3
rowSpacing: 8
columnSpacing: 6
FlyFadeEnterChoreographable {
Layout.fillWidth: true
Layout.columnSpan: 3
StyledText {
width: parent.width
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
text: root.hasFocusedWindow ? Translation.tr("Window options") : Translation.tr("Launch")
// font.pixelSize: Appearance.font.pixelSize.title
}
}
FlyFadeEnterChoreographable {
visible: !root.hasFocusedWindow
PopupLabeledIconButton {
materialSymbol: "terminal"
text: Translation.tr("Terminal")
onClicked: Quickshell.execDetached(["bash", "-c", Config.options.apps.terminal]);
}
}
FlyFadeEnterChoreographable {
visible: !root.hasFocusedWindow
PopupLabeledIconButton {
materialSymbol: "files"
text: Translation.tr("Files")
onClicked: Qt.openUrlExternally(Directories.home);
}
}
FlyFadeEnterChoreographable {
visible: !root.hasFocusedWindow
PopupLabeledIconButton {
materialSymbol: "language"
text: Translation.tr("Browser")
// Kinda hacky. Works with Google and DDG at least
onClicked: Qt.openUrlExternally(Config.options.search.engineBaseUrl);
}
}
FlyFadeEnterChoreographable {
visible: root.hasFocusedWindow
PopupLabeledIconButton {
materialSymbol: "content_copy"
text: Translation.tr("Address")
onClicked: Quickshell.clipboardText = root.activeHyprlandClient.address
}
}
FlyFadeEnterChoreographable {
visible: root.hasFocusedWindow
PopupLabeledIconButton {
property bool toFloat: !(root.activeHyprlandClient?.floating ?? false)
materialSymbol: toFloat ? "picture_in_picture_center" : "side_navigation"
text: toFloat ? Translation.tr("Float") : Translation.tr("Tile")
onClicked: {
Hyprland.dispatch(`hl.dsp.window.float({action = "toggle", window = {address=${root.activeHyprlandClient.address}}})`)
HyprlandData.updateWindowList()
}
}
}
FlyFadeEnterChoreographable {
visible: root.hasFocusedWindow
PopupLabeledIconButton {
materialSymbol: "warning"
text: Translation.tr("Kill")
colBackground: Appearance.colors.colError
colForeground: Appearance.colors.colOnError
onClicked: {
Hyprland.dispatch(`hl.dsp.window.kill({window = {address=${root.activeHyprlandClient.address}}})`)
HyprlandData.updateWindowList()
}
}
}
}
}
component PopupLabeledIconButton: Column {
id: licobtn
property string materialSymbol: "circle"
property string text: "Label"
property alias colBackground: btn.colBackground
property alias colForeground: btn.colForeground
spacing: 4
signal clicked()
onClicked: root.showPopup = false
StyledIconButton {
id: btn
implicitWidth: 70
implicitHeight: 50
text: licobtn.materialSymbol
iconSize: 24
colBackground: Appearance.colors.colLayer4
colForeground: Appearance.colors.colOnLayer4
onClicked: licobtn.clicked()
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: licobtn.text
}
}
}
@@ -0,0 +1,441 @@
pragma ComponentBehavior: Bound
import qs
import qs.modules.common
import qs.modules.common.models
import qs.modules.common.widgets
import qs.modules.common.functions
import qs.services
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import ".."
HBarWidgetContainer {
id: containerRoot
ButtonMouseArea {
id: root
Layout.topMargin: (5 + 5 * containerRoot.startSide) * containerRoot.vertical
Layout.bottomMargin: (5 + 5 * containerRoot.endSide) * containerRoot.vertical
Layout.leftMargin: (3 + 4 * containerRoot.startSide) * !containerRoot.vertical
Layout.rightMargin: (3 + 4 * containerRoot.endSide) * !containerRoot.vertical
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen)
WorkspaceModel {
id: wsModel
monitor: root.monitor
}
property bool vertical: Config.options.bar.vertical
property bool superPressAndHeld: false // Relevant modifications at bottom of file
property real workspaceButtonWidth: 26
property real activeWorkspaceMargin: 2
property real activeWorkspaceSize: workspaceButtonWidth - activeWorkspaceMargin * 2
property real workspaceIconSize: workspaceButtonWidth * 0.69
property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55
property real workspaceIconOpacityShrinked: 1
property real workspaceIconMarginShrinked: -4
property int workspaceIndexInGroup: (monitor?.activeWorkspace?.id - 1) % wsModel.shownCount
property real specialTextSize: workspaceButtonWidth * 0.5
Layout.alignment: vertical ? Qt.AlignHCenter : Qt.AlignVCenter
Layout.fillWidth: vertical
Layout.fillHeight: !vertical
readonly property real barThickness: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.barHeight
implicitWidth: vertical ? barThickness : occupiedIndicators.implicitWidth
implicitHeight: vertical ? occupiedIndicators.implicitHeight : barThickness
property real specialBlur: (wsModel.specialWorkspaceActive && !containsMouse) ? 1 : 0
Behavior on specialBlur {
animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
}
// Interactions
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
property int hoverIndex: {
const position = root.vertical ? mouseY : mouseX;
return Math.floor(position / root.workspaceButtonWidth);
}
function switchWorkspaceToHovered() {
Hyprland.dispatch(`hl.dsp.focus({workspace = ${wsModel.getWorkspaceIdAt(hoverIndex)}})`)
}
onPressed: (mouse) => {
if (mouse.button == Qt.LeftButton)
switchWorkspaceToHovered()
else if (mouse.button == Qt.RightButton)
GlobalStates.overviewOpen = !GlobalStates.overviewOpen;
}
onWheel: (event) => {
if (event.angleDelta.y < 0)
Hyprland.dispatch(`hl.dsp.focus({workspace = "r+1"})`);
else if (event.angleDelta.y > 0)
Hyprland.dispatch(`hl.dsp.focus({workspace = "r-1"})`);
}
// Indications
Item {
id: regularWorkspaces
anchors.fill: parent
scale: 1 - 0.08 * root.specialBlur
layer.smooth: true
layer.enabled: root.specialBlur > 0
layer.effect: MultiEffect {
brightness: -0.1 * root.specialBlur
blurEnabled: true
blur: root.specialBlur
blurMax: 32
}
/////////////////// Occupied indicators ///////////////////
StyledRectangle {
id: occupiedIndicatorsBg
anchors.fill: parent
contentLayer: StyledRectangle.ContentLayer.Group
color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4)
visible: false
}
WorkspaceLayout {
id: occupiedIndicators
anchors.centerIn: parent
layer.enabled: true
visible: false
Repeater {
model: wsModel.shownCount
delegate: Item {
id: wsBg
required property int index
readonly property int wsId: wsModel.getWorkspaceIdAt(index)
property bool currentOccupied: wsModel.occupied[index] && wsId != wsModel.fakeWorkspace
property bool previousOccupied: index > 0 && wsModel.occupied[index - 1] && (wsId - 1) != wsModel.fakeWorkspace
property bool nextOccupied: index < wsModel.shownCount - 1 && wsModel.occupied[index + 1] && (wsId + 1) != wsModel.fakeWorkspace
implicitWidth: root.workspaceButtonWidth
implicitHeight: root.workspaceButtonWidth
// The idea: over-stretch to occupied sides, animate this for a smooth transition.
// masking already prevents weird overlaps
Pill {
property real undirectionalWidth: root.workspaceButtonWidth * wsBg.currentOccupied
property real undirectionalLength: root.workspaceButtonWidth * (1 + 0.5 * wsBg.previousOccupied + 0.5 * wsBg.nextOccupied) * currentOccupied
property real undirectionalOffset: (!wsBg.currentOccupied ? 0.5 : -0.5 * wsBg.previousOccupied) * root.workspaceButtonWidth
anchors.verticalCenter: root.vertical ? undefined : parent.verticalCenter
anchors.horizontalCenter: root.vertical ? parent.horizontalCenter : undefined
x: root.vertical ? 0 : undirectionalOffset
y: root.vertical ? undirectionalOffset : 0
implicitWidth: root.vertical ? undirectionalWidth : undirectionalLength
implicitHeight: root.vertical ? undirectionalLength : undirectionalWidth
Behavior on undirectionalWidth {
animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
}
Behavior on undirectionalLength {
animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
}
Behavior on undirectionalOffset {
animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
}
}
}
}
}
MaskMultiEffect {
id: occupiedIndicatorsMultiEffect
z: 1
anchors.centerIn: parent
implicitWidth: occupiedIndicators.implicitWidth
implicitHeight: occupiedIndicators.implicitHeight
source: occupiedIndicatorsBg
maskSource: occupiedIndicators
}
/////////////////// Active indicator ///////////////////
TrailingIndicator {
id: activeIndicator
anchors.fill: parent
z: 2
index: root.workspaceIndexInGroup
}
/////////////////// Hover ///////////////////
TrailingIndicator {
id: interactionIndicator
z: 3
index: root.containsMouse ? root.hoverIndex : root.workspaceIndexInGroup
color: "transparent"
StateOverlay {
id: hoverOverlay
anchors.fill: interactionIndicator.indicatorRectangle
radius: root.activeWorkspaceSize / 2
hover: root.containsMouse
press: root.containsPress
drag: true // There are too many layers so we need to force this to be a lil more opaque
contentColor: Appearance.colors.colPrimary
}
}
/////////////////// Numbers ///////////////////
WorkspaceLayout {
id: numbersGrid
z: 4
layer.enabled: true // For the masking
Repeater {
model: wsModel.shownCount
delegate: NumberWorkspaceItem {}
}
}
Colorizer {
z: 5
anchors.fill: numbersGrid
colorizationColor: Appearance.colors.colOnPrimary
sourceColor: Appearance.colors.colOnSecondaryContainer
source: activeIndicator
maskEnabled: true
maskSource: numbersGrid
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
/////////////////// App icons ///////////////////
WorkspaceLayout {
id: appsGrid
z: 6
Repeater {
model: wsModel.shownCount
delegate: WorkspaceItem {
id: wsApp
property var biggestWindow: wsModel.biggestWindow[index]
property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing")
AppIcon {
id: appIcon
property real cornerMargin: (!root.superPressAndHeld && Config.options?.bar.workspaces.showAppIcons && wsApp.biggestWindow) ? (root.workspaceButtonWidth - root.workspaceIconSize) / 2 : root.workspaceIconMarginShrinked
anchors {
bottom: parent.bottom
right: parent.right
bottomMargin: (parent.implicitHeight - root.workspaceButtonWidth) / 2 + cornerMargin
rightMargin: (parent.implicitWidth - root.workspaceButtonWidth) / 2 + cornerMargin
}
animated: !wsApp.biggestWindow // Prevent the "image-missing" icon
visible: false // Prevent dupe: the colorizer already copies the icon
source: wsApp.mainAppIconSource
implicitSize: NumberUtils.roundToEven(root.workspaceIconSize)
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on cornerMargin {
animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
}
}
Circle {
id: iconMask
visible: false
layer.enabled: true
diameter: appIcon.implicitSize
}
Loader { // Somehow putting this multieffect in a loader prevents it from not showing up
id: colorizer
anchors.fill: appIcon
sourceComponent: Colorizer {
implicitWidth: appIcon.implicitWidth
implicitHeight: appIcon.implicitHeight
colorizationColor: Appearance.m3colors.darkmode ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary
colorization: Config.options.bar.workspaces.monochromeIcons ? 0.8 : 0.5
brightness: 0
source: appIcon
opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 : (wsApp.biggestWindow && !root.superPressAndHeld && Config.options?.bar.workspaces.showAppIcons) ? 1 : wsApp.biggestWindow ? root.workspaceIconOpacityShrinked : 0
visible: opacity > 0
scale: ((!root.superPressAndHeld && Config.options?.bar.workspaces.showAppIcons) ? root.workspaceIconSize : root.workspaceIconSizeShrinked) / root.workspaceIconSize
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on scale {
animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
}
maskEnabled: true
maskSource: iconMask
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
}
}
}
}
}
FadeLoader {
anchors.centerIn: parent
shown: wsModel.specialWorkspaceActive
scale: 0.8 + 0.2 * root.specialBlur
opacity: root.specialBlur
Behavior on opacity {} // Don't animate, as specialBlur is already animated
sourceComponent: Pill {
anchors.centerIn: parent
property real undirectionalWidth: root.activeWorkspaceSize
property real undirectionalLength: {
const base = root.workspaceButtonWidth * Math.min(1.35, wsModel.shownCount); // Who tf only configures only 2 workspaces shown anyway?
if (root.vertical)
return base;
return specialWsText.implicitWidth + undirectionalWidth;
}
color: Appearance.colors.colPrimary
implicitWidth: root.vertical ? undirectionalWidth : undirectionalLength
implicitHeight: root.vertical ? undirectionalLength : undirectionalWidth
StyledText {
id: specialWsText
anchors.centerIn: parent
text: (!root.vertical ? wsModel.specialWorkspaceName : "S")
color: Appearance.colors.colOnPrimary
font.pixelSize: root.specialTextSize
}
Behavior on undirectionalLength {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
}
}
/////////////////// Super key press handling ///////////////////
Timer {
id: superPressAndHeldTimer
interval: (Config?.options.bar.autoHide.showWhenPressingSuper.delay ?? 100)
repeat: false
onTriggered: {
root.superPressAndHeld = true;
}
}
Connections {
target: GlobalStates
function onSuperDownChanged() {
if (!Config?.options.bar.autoHide.showWhenPressingSuper.enable)
return;
if (GlobalStates.superDown)
superPressAndHeldTimer.restart();
else {
superPressAndHeldTimer.stop();
root.superPressAndHeld = false;
}
}
function onSuperReleaseMightTriggerChanged() {
superPressAndHeldTimer.stop();
}
}
}
/////////////////// Components ///////////////////
component WorkspaceLayout: Box {
anchors {
top: !root.vertical ? parent.top : undefined
bottom: !root.vertical ? parent.bottom : undefined
left: root.vertical ? parent.left : undefined
right: root.vertical ? parent.right : undefined
}
rowSpacing: 0
columnSpacing: 0
vertical: root.vertical
}
component WorkspaceItem: Item {
required property int index
readonly property int wsId: wsModel.getWorkspaceIdAt(index)
implicitWidth: root.vertical ? root.barThickness : root.workspaceButtonWidth
implicitHeight: root.vertical ? root.workspaceButtonWidth : root.barThickness
}
component NumberWorkspaceItem: WorkspaceItem {
id: wsNum
property bool hasBiggestWindow: !!wsModel.biggestWindow[index]
property int wsId: wsModel.getWorkspaceIdAt(index)
property color contentColor: (wsModel.occupied[wsNum.index] && wsId !== wsModel.fakeWorkspace) ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer1Inactive
property bool showingNumbers: {
if (root.superPressAndHeld) return true;
if (GlobalStates.screenLocked) return false;
if (Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !wsNum.hasBiggestWindow)) return true;
return false;
}
FadeLoader {
shown: !wsNum.showingNumbers
anchors.centerIn: parent
Circle {
anchors.centerIn: parent
diameter: root.workspaceButtonWidth * 0.18
color: wsNum.contentColor
}
}
FadeLoader {
shown: wsNum.showingNumbers
anchors.centerIn: parent
StyledText {
anchors.centerIn: parent
font {
pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== "10") * 2)
family: Config.options?.bar.workspaces.useNerdFont ? Appearance.font.family.iconNerd : defaultFont
}
color: wsNum.contentColor
text: Config.options?.bar.workspaces.numberMap[wsNum.wsId - 1] || wsNum.wsId
}
}
}
component TrailingIndicator: Item {
id: trailingIndicator
anchors.fill: parent
required property int index
property alias indicatorRectangle: indicatorRect
property alias color: indicatorRect.color
property var indexPair: AnimatedTabIndexPair {
id: idxPair
index: trailingIndicator.index
}
StyledRectangle {
id: indicatorRect
anchors {
verticalCenter: root.vertical ? undefined : parent.verticalCenter
horizontalCenter: root.vertical ? parent.horizontalCenter : undefined
}
property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * root.workspaceButtonWidth + root.activeWorkspaceMargin
property real indicatorLength: Math.abs(idxPair.idx1 - idxPair.idx2) * root.workspaceButtonWidth + root.activeWorkspaceSize
property real indicatorThickness: root.activeWorkspaceSize
contentLayer: StyledRectangle.ContentLayer.Group
radius: indicatorThickness / 2
color: Appearance.colors.colPrimary
x: root.vertical ? null : indicatorPosition
y: root.vertical ? indicatorPosition : null
implicitWidth: root.vertical ? indicatorThickness : indicatorLength
implicitHeight: root.vertical ? indicatorLength : indicatorThickness
}
}
}
@@ -0,0 +1,12 @@
import QtQuick
import QtQuick.Layouts
import qs.modules.ii.bar as IIBar
import qs.modules.common as C
IIBar.Workspaces {
id: root
vertical: C.Config.options.bar.vertical
Layout.alignment: vertical ? Qt.AlignHCenter : Qt.AlignVCenter
Layout.fillWidth: vertical
Layout.fillHeight: !vertical
}
@@ -0,0 +1,9 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
Item {
visible: false
Layout.fillWidth: false
Layout.fillHeight: false
}
@@ -0,0 +1,9 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
Item {
visible: false
Layout.fillHeight: true
Layout.fillWidth: true
}
@@ -12,9 +12,8 @@ Item {
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen) readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
property string activeWindowAddress: `0x${activeWindow?.HyprlandToplevel?.address}` readonly property bool focusingThisMonitor: HyprlandData.activeWorkspace?.monitor == monitor?.name
property bool focusingThisMonitor: HyprlandData.activeWorkspace?.monitor == monitor?.name readonly property var biggestWindow: HyprlandData.biggestWindowForWorkspace(HyprlandData.monitors[root.monitor?.id]?.activeWorkspace.id)
property var biggestWindow: HyprlandData.biggestWindowForWorkspace(HyprlandData.monitors[root.monitor?.id]?.activeWorkspace.id)
implicitWidth: colLayout.implicitWidth implicitWidth: colLayout.implicitWidth
@@ -303,7 +303,7 @@ Item { // Bar content region
} }
} }
MaterialSymbol { MaterialSymbol {
text: Network.materialSymbol text: Icons.getNetworkMaterialSymbol()
iconSize: Appearance.font.pixelSize.larger iconSize: Appearance.font.pixelSize.larger
color: rightSidebarButton.colText color: rightSidebarButton.colText
} }
@@ -1,4 +1,5 @@
import qs.modules.common import qs.modules.common
import qs.modules.common.widgets
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
@@ -23,9 +24,9 @@ Item {
radius: Appearance.rounding.small radius: Appearance.rounding.small
} }
GridLayout { BoxLayout {
id: gridLayout id: gridLayout
columns: root.vertical ? 1 : -1 vertical: root.vertical
anchors { anchors {
verticalCenter: root.vertical ? undefined : parent.verticalCenter verticalCenter: root.vertical ? undefined : parent.verticalCenter
horizontalCenter: root.vertical ? parent.horizontalCenter : undefined horizontalCenter: root.vertical ? parent.horizontalCenter : undefined
@@ -1,4 +1,5 @@
import qs.modules.common import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets import qs.modules.common.widgets
import qs.services import qs.services
import QtQuick import QtQuick
@@ -14,7 +15,7 @@ StyledPopup {
// Header // Header
StyledPopupHeaderRow { StyledPopupHeaderRow {
icon: "battery_android_full" icon: Icons.getBatteryIcon(Battery.percentage * 100)
label: Translation.tr("Battery") label: Translation.tr("Battery")
} }
@@ -27,18 +28,10 @@ StyledPopup {
icon: "schedule" icon: "schedule"
label: Battery.isCharging ? Translation.tr("Time to full:") : Translation.tr("Time to empty:") label: Battery.isCharging ? Translation.tr("Time to full:") : Translation.tr("Time to empty:")
value: { value: {
function formatTime(seconds) {
var h = Math.floor(seconds / 3600);
var m = Math.floor((seconds % 3600) / 60);
if (h > 0)
return `${h}h, ${m}m`;
else
return `${m}m`;
}
if (Battery.isCharging) if (Battery.isCharging)
return formatTime(Battery.timeToFull); return DateUtils.formatDuration(Battery.timeToFull);
else else
return formatTime(Battery.timeToEmpty); return DateUtils.formatDuration(Battery.timeToEmpty);
} }
} }
@@ -54,13 +47,7 @@ StyledPopup {
return Translation.tr("Discharging:"); return Translation.tr("Discharging:");
} }
} }
value: { value: `${Battery.energyRate.toFixed(2)}W`
if (Battery.chargeState == 4) {
return "";
} else {
return `${Battery.energyRate.toFixed(2)}W`;
}
}
} }
StyledPopupValueRow { StyledPopupValueRow {
@@ -6,7 +6,6 @@ import QtQuick.Layouts
Item { Item {
id: root id: root
property bool borderless: Config.options.bar.borderless
property bool showDate: Config.options.bar.verbose property bool showDate: Config.options.bar.verbose
implicitWidth: rowLayout.implicitWidth implicitWidth: rowLayout.implicitWidth
implicitHeight: Appearance.sizes.barHeight implicitHeight: Appearance.sizes.barHeight
@@ -55,8 +55,8 @@ RippleButton {
CustomIcon { CustomIcon {
id: distroIcon id: distroIcon
anchors.centerIn: parent anchors.centerIn: parent
width: 19.5 width: 20
height: 19.5 height: 20
source: Config.options.bar.topLeftIcon == 'distro' ? SystemInfo.distroIcon : `${Config.options.bar.topLeftIcon}-symbolic` source: Config.options.bar.topLeftIcon == 'distro' ? SystemInfo.distroIcon : `${Config.options.bar.topLeftIcon}-symbolic`
colorize: true colorize: true
color: Appearance.colors.colOnLayer0 color: Appearance.colors.colOnLayer0
@@ -7,11 +7,6 @@ import QtQuick.Layouts
StyledPopup { StyledPopup {
id: root id: root
// Helper function to format KB to GB
function formatKB(kb) {
return (kb / (1024 * 1024)).toFixed(1) + " GB";
}
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: 12 spacing: 12
@@ -29,17 +24,17 @@ StyledPopup {
StyledPopupValueRow { StyledPopupValueRow {
icon: "clock_loader_60" icon: "clock_loader_60"
label: Translation.tr("Used:") label: Translation.tr("Used:")
value: root.formatKB(ResourceUsage.memoryUsed) value: ResourceUsage.kbToGbString(ResourceUsage.memoryUsed)
} }
StyledPopupValueRow { StyledPopupValueRow {
icon: "check_circle" icon: "check_circle"
label: Translation.tr("Free:") label: Translation.tr("Free:")
value: root.formatKB(ResourceUsage.memoryFree) value: ResourceUsage.kbToGbString(ResourceUsage.memoryFree)
} }
StyledPopupValueRow { StyledPopupValueRow {
icon: "empty_dashboard" icon: "empty_dashboard"
label: Translation.tr("Total:") label: Translation.tr("Total:")
value: root.formatKB(ResourceUsage.memoryTotal) value: ResourceUsage.kbToGbString(ResourceUsage.memoryTotal)
} }
} }
} }
@@ -58,17 +53,17 @@ StyledPopup {
StyledPopupValueRow { StyledPopupValueRow {
icon: "clock_loader_60" icon: "clock_loader_60"
label: Translation.tr("Used:") label: Translation.tr("Used:")
value: root.formatKB(ResourceUsage.swapUsed) value: ResourceUsage.kbToGbString(ResourceUsage.swapUsed)
} }
StyledPopupValueRow { StyledPopupValueRow {
icon: "check_circle" icon: "check_circle"
label: Translation.tr("Free:") label: Translation.tr("Free:")
value: root.formatKB(ResourceUsage.swapFree) value: ResourceUsage.kbToGbString(ResourceUsage.swapFree)
} }
StyledPopupValueRow { StyledPopupValueRow {
icon: "empty_dashboard" icon: "empty_dashboard"
label: Translation.tr("Total:") label: Translation.tr("Total:")
value: root.formatKB(ResourceUsage.swapTotal) value: ResourceUsage.kbToGbString(ResourceUsage.swapTotal)
} }
} }
} }
@@ -65,9 +65,9 @@ Item {
} }
} }
GridLayout { BoxLayout {
id: gridLayout id: gridLayout
columns: root.vertical ? 1 : -1 vertical: root.vertical
anchors.fill: parent anchors.fill: parent
rowSpacing: 8 rowSpacing: 8
columnSpacing: 15 columnSpacing: 15
@@ -9,7 +9,7 @@ import qs.modules.common
import qs.modules.common.widgets import qs.modules.common.widgets
import qs.modules.common.functions import qs.modules.common.functions
MouseArea { ButtonMouseArea {
id: root id: root
required property SystemTrayItem item required property SystemTrayItem item
property bool targetMenuOpen: false property bool targetMenuOpen: false
@@ -19,8 +19,10 @@ MouseArea {
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
implicitWidth: 20 property real iconSize: 20
implicitHeight: 20 property real backgroundSize: 26
implicitWidth: iconSize
implicitHeight: iconSize
onPressed: (event) => { onPressed: (event) => {
switch (event.button) { switch (event.button) {
case Qt.LeftButton: case Qt.LeftButton:
@@ -40,6 +42,16 @@ MouseArea {
tooltip.text = TrayService.getTooltipForItem(root.item); tooltip.text = TrayService.getTooltipForItem(root.item);
} }
StateOverlay {
id: hoverOverlay
anchors.centerIn: parent
width: root.backgroundSize
height: root.backgroundSize
radius: root.backgroundSize / 2
hover: root.containsMouse
press: root.containsPress
}
Loader { Loader {
id: menu id: menu
function open() { function open() {
@@ -73,8 +85,8 @@ MouseArea {
visible: !Config.options.tray.monochromeIcons visible: !Config.options.tray.monochromeIcons
source: root.item.icon source: root.item.icon
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width width: root.iconSize
height: parent.height height: root.iconSize
} }
Loader { Loader {
@@ -187,7 +187,10 @@ PopupWindow {
Layout.fillWidth: true Layout.fillWidth: true
visible: root.trayItemId !== undefined && root.trayItemId.length > 0 && stackView.depth === 1 visible: root.trayItemId !== undefined && root.trayItemId.length > 0 && stackView.depth === 1
releaseAction: () => TrayService.togglePin(root.trayItemId); releaseAction: () => {
GlobalFocusGrab.dismiss();
TrayService.togglePin(root.trayItemId);
}
contentItem: RowLayout { contentItem: RowLayout {
anchors { anchors {
@@ -1,3 +1,4 @@
pragma ComponentBehavior: Bound
import qs import qs
import qs.services import qs.services
import qs.modules.common import qs.modules.common
@@ -15,22 +16,22 @@ import Qt5Compat.GraphicalEffects
Item { Item {
id: root id: root
property bool vertical: false property bool vertical: false
property bool borderless: Config.options.bar.borderless
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen) readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
readonly property int effectiveActiveWorkspaceId: monitor?.activeWorkspace?.id ?? 1 readonly property bool activeActuallyFocused: activeWindow?.activated ?? false
readonly property int workspacesShown: Config.options.bar.workspaces.shown WorkspaceModel {
readonly property int workspaceGroup: Math.floor((effectiveActiveWorkspaceId - 1) / root.workspacesShown) id: wsModel
property list<bool> workspaceOccupied: [] monitor: root.monitor
property int widgetPadding: 4 }
property int workspaceButtonWidth: 26 property int workspaceButtonWidth: 26
property real activeWorkspaceMargin: 2 property real activeWorkspaceMargin: 2
property real workspaceIconSize: workspaceButtonWidth * 0.69 property real workspaceIconSize: workspaceButtonWidth * 0.69
property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55 property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55
property real workspaceIconOpacityShrinked: 1 property real workspaceIconOpacityShrinked: 1
property real workspaceIconMarginShrinked: -4 property real workspaceIconMarginShrinked: -4
property int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown property int workspaceIndexInGroup: (monitor?.activeWorkspace?.id - 1) % wsModel.shownCount
property bool showNumbers: false property bool showNumbers: false
Timer { Timer {
@@ -56,33 +57,8 @@ Item {
} }
} }
// Function to update workspaceOccupied implicitWidth: root.vertical ? Appearance.sizes.verticalBarWidth : (root.workspaceButtonWidth * wsModel.shownCount)
function updateWorkspaceOccupied() { implicitHeight: root.vertical ? (root.workspaceButtonWidth * wsModel.shownCount) : Appearance.sizes.barHeight
workspaceOccupied = Array.from({ length: root.workspacesShown }, (_, i) => {
return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * root.workspacesShown + i + 1);
})
}
// Occupied workspace updates
Component.onCompleted: updateWorkspaceOccupied()
Connections {
target: Hyprland.workspaces
function onValuesChanged() {
updateWorkspaceOccupied();
}
}
Connections {
target: Hyprland
function onFocusedWorkspaceChanged() {
updateWorkspaceOccupied();
}
}
onWorkspaceGroupChanged: {
updateWorkspaceOccupied();
}
implicitWidth: root.vertical ? Appearance.sizes.verticalBarWidth : (root.workspaceButtonWidth * root.workspacesShown)
implicitHeight: root.vertical ? (root.workspaceButtonWidth * root.workspacesShown) : Appearance.sizes.barHeight
// Scroll to switch workspaces // Scroll to switch workspaces
WheelHandler { WheelHandler {
@@ -106,25 +82,27 @@ Item {
} }
// Workspaces - background // Workspaces - background
Grid { Box {
z: 1 z: 1
anchors.centerIn: parent anchors.centerIn: parent
rowSpacing: 0 rowSpacing: 0
columnSpacing: 0 columnSpacing: 0
columns: root.vertical ? 1 : root.workspacesShown vertical: root.vertical
rows: root.vertical ? root.workspacesShown : 1
Repeater { Repeater {
model: root.workspacesShown model: wsModel.shownCount
delegate: Rectangle {
required property int index
Rectangle {
z: 1 z: 1
implicitWidth: workspaceButtonWidth implicitWidth: root.workspaceButtonWidth
implicitHeight: workspaceButtonWidth implicitHeight: root.workspaceButtonWidth
radius: (width / 2) radius: (width / 2)
property var previousOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index)) property bool thisOccupied: (wsModel.occupied[index] && !(!wsModel.currentWorkspaceNotFake && monitor?.activeWorkspace?.id === index+1))
property var rightOccupied: (workspaceOccupied[index+1] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index+2)) property var previousOccupied: (wsModel.occupied[index-1] && !(!wsModel.currentWorkspaceNotFake && monitor?.activeWorkspace?.id === index))
property var rightOccupied: (wsModel.occupied[index+1] && !(!wsModel.currentWorkspaceNotFake && monitor?.activeWorkspace?.id === index+2))
property var radiusPrev: previousOccupied ? 0 : (width / 2) property var radiusPrev: previousOccupied ? 0 : (width / 2)
property var radiusNext: rightOccupied ? 0 : (width / 2) property var radiusNext: rightOccupied ? 0 : (width / 2)
@@ -134,7 +112,7 @@ Item {
bottomRightRadius: radiusNext bottomRightRadius: radiusNext
color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4) color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4)
opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index+1)) ? 1 : 0 opacity: thisOccupied ? 1 : 0
Behavior on opacity { Behavior on opacity {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this) animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
@@ -169,9 +147,9 @@ Item {
id: idxPair id: idxPair
index: root.workspaceIndexInGroup index: root.workspaceIndexInGroup
} }
property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * workspaceButtonWidth + root.activeWorkspaceMargin property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * root.workspaceButtonWidth + root.activeWorkspaceMargin
property real indicatorLength: Math.abs(idxPair.idx1 - idxPair.idx2) * workspaceButtonWidth + workspaceButtonWidth - root.activeWorkspaceMargin * 2 property real indicatorLength: Math.abs(idxPair.idx1 - idxPair.idx2) * root.workspaceButtonWidth + root.workspaceButtonWidth - root.activeWorkspaceMargin * 2
property real indicatorThickness: workspaceButtonWidth - root.activeWorkspaceMargin * 2 property real indicatorThickness: root.workspaceButtonWidth - root.activeWorkspaceMargin * 2
x: root.vertical ? null : indicatorPosition x: root.vertical ? null : indicatorPosition
implicitWidth: root.vertical ? indicatorThickness : indicatorLength implicitWidth: root.vertical ? indicatorThickness : indicatorLength
@@ -181,22 +159,21 @@ Item {
} }
// Workspaces - numbers // Workspaces - numbers
Grid { Box {
id: wsNumbers
z: 3 z: 3
anchors.fill: parent
columns: root.vertical ? 1 : root.workspacesShown vertical: root.vertical
rows: root.vertical ? root.workspacesShown : 1
columnSpacing: 0 columnSpacing: 0
rowSpacing: 0 rowSpacing: 0
anchors.fill: parent
Repeater { Repeater {
model: root.workspacesShown model: wsModel.shownCount
delegate: Button {
Button {
id: button id: button
property int workspaceValue: workspaceGroup * root.workspacesShown + index + 1 required property int index
property int workspaceValue: wsModel.getWorkspaceIdAt(index)
implicitHeight: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.barHeight implicitHeight: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.barHeight
implicitWidth: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.verticalBarWidth implicitWidth: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.verticalBarWidth
onPressed: Hyprland.dispatch(`hl.dsp.focus({ workspace = ${workspaceValue}})`) onPressed: Hyprland.dispatch(`hl.dsp.focus({ workspace = ${workspaceValue}})`)
@@ -205,11 +182,16 @@ Item {
background: Item { background: Item {
id: workspaceButtonBackground id: workspaceButtonBackground
implicitWidth: workspaceButtonWidth implicitWidth: root.workspaceButtonWidth
implicitHeight: workspaceButtonWidth implicitHeight: root.workspaceButtonWidth
property var biggestWindow: HyprlandData.biggestWindowForWorkspace(button.workspaceValue) property var biggestWindow: HyprlandData.biggestWindowForWorkspace(button.workspaceValue)
property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing") property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing")
property color numberColor: (monitor?.activeWorkspace?.id == button.workspaceValue) ?
Appearance.m3colors.m3onPrimary :
(wsModel.occupied[index] ? Appearance.m3colors.m3onSecondaryContainer :
Appearance.colors.colOnLayer1Inactive)
StyledText { // Workspace number text StyledText { // Workspace number text
opacity: root.showNumbers opacity: root.showNumbers
|| ((Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || root.showNumbers)) || ((Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || root.showNumbers))
@@ -226,10 +208,7 @@ Item {
} }
text: Config.options?.bar.workspaces.numberMap[button.workspaceValue - 1] || button.workspaceValue text: Config.options?.bar.workspaces.numberMap[button.workspaceValue - 1] || button.workspaceValue
elide: Text.ElideRight elide: Text.ElideRight
color: (root.effectiveActiveWorkspaceId == button.workspaceValue) ? color: workspaceButtonBackground.numberColor
Appearance.m3colors.m3onPrimary :
(workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer :
Appearance.colors.colOnLayer1Inactive)
Behavior on opacity { Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
@@ -243,13 +222,10 @@ Item {
) ? 0 : 1 ) ? 0 : 1
visible: opacity > 0 visible: opacity > 0
anchors.centerIn: parent anchors.centerIn: parent
width: workspaceButtonWidth * 0.18 width: root.workspaceButtonWidth * 0.18
height: width height: width
radius: width / 2 radius: width / 2
color: (root.effectiveActiveWorkspaceId == button.workspaceValue) ? color: workspaceButtonBackground.numberColor
Appearance.m3colors.m3onPrimary :
(workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer :
Appearance.colors.colOnLayer1Inactive)
Behavior on opacity { Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
@@ -257,8 +233,8 @@ Item {
} }
Item { // Main app icon Item { // Main app icon
anchors.centerIn: parent anchors.centerIn: parent
width: workspaceButtonWidth width: root.workspaceButtonWidth
height: workspaceButtonWidth height: root.workspaceButtonWidth
opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 : opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 :
(workspaceButtonBackground.biggestWindow && !root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? (workspaceButtonBackground.biggestWindow && !root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ?
1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0
@@ -268,9 +244,9 @@ Item {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.bottomMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? anchors.bottomMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ?
(workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked (root.workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked
anchors.rightMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? anchors.rightMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ?
(workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked (root.workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked
source: workspaceButtonBackground.mainAppIconSource source: workspaceButtonBackground.mainAppIconSource
implicitSize: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked implicitSize: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked
@@ -309,12 +285,8 @@ Item {
} }
} }
} }
} }
} }
} }
} }
@@ -303,7 +303,7 @@ MouseArea {
IconAndTextPair { IconAndTextPair {
visible: Battery.available visible: Battery.available
icon: Battery.isCharging ? "bolt" : "battery_android_full" icon: Battery.isCharging ? "bolt" : Icons.getBatteryIcon(Battery.percentage * 100)
text: Math.round(Battery.percentage * 100) text: Math.round(Battery.percentage * 100)
color: (Battery.isLow && !Battery.isCharging) ? Appearance.colors.colError : Appearance.colors.colOnSurfaceVariant color: (Battery.isLow && !Battery.isCharging) ? Appearance.colors.colError : Appearance.colors.colOnSurfaceVariant
} }
@@ -92,7 +92,7 @@ Rectangle {
id: boltIcon id: boltIcon
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
fill: 1 fill: 1
text: Battery.isCharging ? "bolt" : "battery_android_full" text: Battery.isCharging ? "bolt" : Icons.getBatteryIcon(Battery.percentage * 100)
color: batteryWidget.colText color: batteryWidget.colText
iconSize: 24 iconSize: 24
animateChange: true animateChange: true
@@ -95,6 +95,25 @@ Item { // Wrapper
root.focusFirstItem(); root.focusFirstItem();
} }
} }
// Ctrl+N / Ctrl+P navigation
if (event.modifiers & Qt.ControlModifier) {
if (event.key === Qt.Key_N) {
if (appResults.visible && appResults.count > 0) {
// Wrap around the list rather than the default arrow key behaviour
appResults.currentIndex = (appResults.currentIndex + 1) % appResults.count;
event.accepted = true;
return;
}
} else if (event.key === Qt.Key_P) {
if (appResults.visible && appResults.count > 0) {
// Wrap around too
appResults.currentIndex = (appResults.count + appResults.currentIndex - 1) % appResults.count;
event.accepted = true;
return;
}
}
}
} }
StyledRectangularShadow { StyledRectangularShadow {
@@ -53,6 +53,7 @@ Scope {
margins: Appearance.sizes.hyprlandGapsOut margins: Appearance.sizes.hyprlandGapsOut
leftMargin: Appearance.sizes.elevationMargin leftMargin: Appearance.sizes.elevationMargin
} }
asynchronous: true
width: sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin width: sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
@@ -40,6 +40,7 @@ Item {
return `${minutes}:${seconds}`; return `${minutes}:${seconds}`;
} }
font.pixelSize: 40 font.pixelSize: 40
font.features: { "tnum": 1 }
color: Appearance.m3colors.m3onSurface color: Appearance.m3colors.m3onSurface
} }
StyledText { StyledText {
@@ -53,6 +53,7 @@ Item {
StyledText { StyledText {
// Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness // Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness
font.pixelSize: 40 font.pixelSize: 40
font.features: { "tnum": 1 }
color: Appearance.m3colors.m3onSurface color: Appearance.m3colors.m3onSurface
text: { text: {
let totalSeconds = Math.floor(TimerService.stopwatchTime) / 100 let totalSeconds = Math.floor(TimerService.stopwatchTime) / 100
@@ -64,9 +65,10 @@ Item {
StyledText { StyledText {
Layout.fillWidth: true Layout.fillWidth: true
font.pixelSize: 40 font.pixelSize: 40
font.features: { "tnum": 1 }
color: Appearance.colors.colSubtext color: Appearance.colors.colSubtext
text: { text: {
return `:<sub>${(Math.floor(TimerService.stopwatchTime) % 100).toString().padStart(2, '0')}</sub>` return `:${(Math.floor(TimerService.stopwatchTime) % 100).toString().padStart(2, '0')}`
} }
} }
} }
@@ -11,7 +11,7 @@ import Quickshell.Hyprland
QuickToggleButton { QuickToggleButton {
toggled: Network.wifiStatus !== "disabled" toggled: Network.wifiStatus !== "disabled"
buttonIcon: Network.materialSymbol buttonIcon: Icons.getNetworkMaterialSymbol()
onClicked: Network.toggleWifi() onClicked: Network.toggleWifi()
altAction: () => { altAction: () => {
Quickshell.execDetached(["bash", "-c", `${Network.ethernet ? Config.options.apps.networkEthernet : Config.options.apps.network}`]) Quickshell.execDetached(["bash", "-c", `${Network.ethernet ? Config.options.apps.networkEthernet : Config.options.apps.network}`])
@@ -273,7 +273,7 @@ Item { // Bar content region
} }
} }
MaterialSymbol { MaterialSymbol {
text: Network.materialSymbol text: Icons.getNetworkMaterialSymbol()
iconSize: Appearance.font.pixelSize.larger iconSize: Appearance.font.pixelSize.larger
color: rightSidebarButton.colText color: rightSidebarButton.colText
} }
@@ -1,19 +1,14 @@
import QtQuick import QtQuick
import org.kde.kirigami as Kirigami
import qs.services import qs.services
import qs.modules.common import qs.modules.common
import qs.modules.common.widgets as W
Kirigami.Icon { W.AppIcon {
id: root id: root
required property string iconName required property string iconName
property bool separateLightDark: false property bool separateLightDark: false
property bool tryCustomIcon: true property bool tryCustomIcon: true
property real implicitSize: 26
implicitWidth: implicitSize
implicitHeight: implicitSize
animated: true
roundToIconSize: false roundToIconSize: false
fallback: root.iconName fallback: root.iconName
source: tryCustomIcon ? `${Looks.iconsPath}/${root.iconName}${!root.separateLightDark ? "" : Looks.dark ? "-dark" : "-light"}.svg` : fallback source: tryCustomIcon ? `${Looks.iconsPath}/${root.iconName}${!root.separateLightDark ? "" : Looks.dark ? "-dark" : "-light"}.svg` : fallback
@@ -24,9 +24,9 @@ Singleton {
property string internetIcon: { property string internetIcon: {
if (Network.ethernet) if (Network.ethernet)
return "ethernet"; return "ethernet";
if (Network.wifiEnabled) { if (Network.wifiEnabled && Network.wifiStatus === "connected") {
const strength = Network.networkStrength; const strength = Network.networkStrength;
return wifiIconForStrength(strength); return root.wifiIconForStrength(strength);
} }
if (Network.wifiStatus === "connecting") if (Network.wifiStatus === "connecting")
return "wifi-4"; return "wifi-4";
@@ -11,10 +11,9 @@ ScrollBar {
active: hovered || pressed active: hovered || pressed
property color color: Looks.colors.controlBg property color color: Looks.colors.controlBg
contentItem: Rectangle { contentItem: Pill {
implicitWidth: root.active ? 4 : 2 implicitWidth: root.active ? 4 : 2
implicitHeight: root.visualSize implicitHeight: root.visualSize
radius: 9999
color: root.color color: root.color
opacity: root.policy === ScrollBar.AlwaysOn || (root.active && root.size < 1.0) ? 0.5 : 0 opacity: root.policy === ScrollBar.AlwaysOn || (root.active && root.size < 1.0) ? 0.5 : 0
@@ -1,31 +1,23 @@
import QtQuick import QtQuick
import qs.modules.common.widgets as W
Item { W.FixedWidthTextContainer {
id: root id: root
property string longestText
property alias text: textItem.text property alias text: textItem.text
property alias font: textItem.font
property alias horizontalAlignment: textItem.horizontalAlignment property alias horizontalAlignment: textItem.horizontalAlignment
property alias verticalAlignment: textItem.verticalAlignment property alias verticalAlignment: textItem.verticalAlignment
property alias color: textItem.color property alias color: textItem.color
implicitWidth: longestTextMetrics.width font {
implicitHeight: longestTextMetrics.height family: Looks.font.family.ui
pixelSize: Looks.font.pixelSize.large
TextMetrics { weight: Looks.font.weight.regular
id: longestTextMetrics
text: root.longestText
font {
family: Looks.font.family.ui
pixelSize: Looks.font.pixelSize.large
weight: Looks.font.weight.regular
}
} }
WText { WText {
id: textItem id: textItem
anchors.fill: parent anchors.fill: parent
font.pixelSize: Looks.font.pixelSize.large font: root.font
} }
} }

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