Compare commits
745 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39a67a4564 | ||
|
|
4b80630b7e | ||
|
|
dee7979cd5 | ||
|
|
8709371e4e | ||
|
|
e4ab87bc2a | ||
|
|
37b57aee60 | ||
|
|
33ef1720e3 | ||
|
|
c41a495ee0 | ||
|
|
47f93e9cab | ||
|
|
9e91ae61a5 | ||
|
|
87cc157055 | ||
|
|
29983ab9da | ||
|
|
ff9db50831 | ||
|
|
4cb6ac05d3 | ||
|
|
73a64944b8 | ||
|
|
d25c2db420 | ||
|
|
216775f55a | ||
|
|
ad79e81c50 | ||
|
|
df7226d06c | ||
|
|
6e03473133 | ||
|
|
36c58e211d | ||
|
|
70de347bcf | ||
|
|
2cbf64e250 | ||
|
|
a14e559c80 | ||
|
|
59fb1b85eb | ||
|
|
4197a863e5 | ||
|
|
4293c6877d | ||
|
|
f393627932 | ||
|
|
c54ce27505 | ||
|
|
97518eb49c | ||
|
|
ddce15481f | ||
|
|
43721426e8 | ||
|
|
6baa9c7858 | ||
|
|
8ec080a290 | ||
|
|
166c440978 | ||
|
|
6e9dca9d42 | ||
|
|
b50f41b1e0 | ||
|
|
357dcaacbc | ||
|
|
61e681e6bd | ||
|
|
ebd4264621 | ||
|
|
4167c64fde | ||
|
|
6456b2998d | ||
|
|
4de5ab3c59 | ||
|
|
04ca79a5af | ||
|
|
0d803fd962 | ||
|
|
8406c86117 | ||
|
|
1aee3163e0 | ||
|
|
9aec974e9e | ||
|
|
4de148be58 | ||
|
|
3f48178333 | ||
|
|
8af01e44f2 | ||
|
|
a76440f242 | ||
|
|
49c0d14ace | ||
|
|
6a6856192c | ||
|
|
b821d8bb6a | ||
|
|
e12d5f7fb9 | ||
|
|
26ee4e5560 | ||
|
|
622a9c1b06 | ||
|
|
6b0e94698b | ||
|
|
08e1f2d5f3 | ||
|
|
b53431d861 | ||
|
|
327b223998 | ||
|
|
e26cd16ef9 | ||
|
|
669e7e9566 | ||
|
|
9bb8a9e3dd | ||
|
|
249cacdd0a | ||
|
|
167fe26f30 | ||
|
|
44a86e0937 | ||
|
|
f5e1b61dcf | ||
|
|
1ca23373e1 | ||
|
|
78dafcc5b3 | ||
|
|
e92ccab3d1 | ||
|
|
8820a0f274 | ||
|
|
9c15f8aed1 | ||
|
|
f5ad63ed33 | ||
|
|
7abc39d051 | ||
|
|
47745fa501 | ||
|
|
4c2e460127 | ||
|
|
fa567b06d0 | ||
|
|
892c09512c | ||
|
|
8be2f01058 | ||
|
|
673ef2e09d | ||
|
|
cd271ed283 | ||
|
|
98e370ac60 | ||
|
|
425ef5020b | ||
|
|
91e7a86e7a | ||
|
|
b369f648db | ||
|
|
bfe1d248a4 | ||
|
|
902ca67c58 | ||
|
|
2ac527bcbc | ||
|
|
60af15fb8d | ||
|
|
b3043afe04 | ||
|
|
9458d95222 | ||
|
|
8f15274fb9 | ||
|
|
19b790d4fa | ||
|
|
74718b064f | ||
|
|
7095cb6637 | ||
|
|
7dc64014fd | ||
|
|
66bc9f6d74 | ||
|
|
4b3240b9b9 | ||
|
|
3803141f31 | ||
|
|
2ed91786a3 | ||
|
|
5e827a73c8 | ||
|
|
58957ec597 | ||
|
|
abafe1b6fd | ||
|
|
6536035e19 | ||
|
|
187a367977 | ||
|
|
e0ae5c97d4 | ||
|
|
59b3cdd0dc | ||
|
|
5cf8659f92 | ||
|
|
0b14629dd7 | ||
|
|
726093f534 | ||
|
|
39a389b90c | ||
|
|
140643d4ec | ||
|
|
f52164da87 | ||
|
|
926c5bae7e | ||
|
|
ac8375bf47 | ||
|
|
58667c77ed | ||
|
|
b06c3e0e3f | ||
|
|
21d50ed7e6 | ||
|
|
5f576315fe | ||
|
|
da57afb395 | ||
|
|
c0ef865920 | ||
|
|
3afc8fa9ca | ||
|
|
7a4191df95 | ||
|
|
18832c2956 | ||
|
|
4974df29d6 | ||
|
|
b72700fbd2 | ||
|
|
361fe49001 | ||
|
|
db5e2055e1 | ||
|
|
3ef6861b62 | ||
|
|
3156b2b85a | ||
|
|
d2f8d3dfe3 | ||
|
|
fc6f154021 | ||
|
|
f3b2f4bce7 | ||
|
|
edad7fb3b7 | ||
|
|
3631b67496 | ||
|
|
b118409e83 | ||
|
|
34706b1ae8 | ||
|
|
8927256f39 | ||
|
|
6d667538db | ||
|
|
54145617b5 | ||
|
|
49057eec5d | ||
|
|
b144571c9a | ||
|
|
30a4b63f00 | ||
|
|
0e2307ea03 | ||
|
|
dcdcc2dbc5 | ||
|
|
9a37ab6e7b | ||
|
|
b398ea4749 | ||
|
|
c29cc74de0 | ||
|
|
f571c406c7 | ||
|
|
517b31d62a | ||
|
|
a6953310b1 | ||
|
|
46591b1b8a | ||
|
|
67c789ae41 | ||
|
|
5ce4cedd3e | ||
|
|
df5c4a0678 | ||
|
|
29176aacf0 | ||
|
|
345a57c465 | ||
|
|
bf1e1622ac | ||
|
|
b9ce839f83 | ||
|
|
8d025d1958 | ||
|
|
732acbdf5c | ||
|
|
975ebc7ee5 | ||
|
|
03baaf3099 | ||
|
|
7ba9d95f96 | ||
|
|
3d08b3e873 | ||
|
|
fa31aa9299 | ||
|
|
7f72d0805e | ||
|
|
ac5432c93c | ||
|
|
19db669204 | ||
|
|
e13bb1f551 | ||
|
|
8fbad0c9be | ||
|
|
994ee6de29 | ||
|
|
0bd437603b | ||
|
|
1891165d50 | ||
|
|
45657e145c | ||
|
|
ca63ebe504 | ||
|
|
66951f8fba | ||
|
|
609e023f9b | ||
|
|
294ae868a6 | ||
|
|
0e15ffa23a | ||
|
|
5f62a00c60 | ||
|
|
bf1b24bb95 | ||
|
|
27dacde798 | ||
|
|
9004f186fa | ||
|
|
b9e6e8ba0c | ||
|
|
ed15ddfaaa | ||
|
|
881910f1b3 | ||
|
|
7936e4f881 | ||
|
|
9072ff8525 | ||
|
|
1c85310c22 | ||
|
|
b65c1b5c23 | ||
|
|
2840138464 | ||
|
|
205fe2f9bf | ||
|
|
6a7e77029d | ||
|
|
f36cb58237 | ||
|
|
c14f2b4fc6 | ||
|
|
243408d176 | ||
|
|
b647eb36af | ||
|
|
4238b5fe9a | ||
|
|
3120d7556e | ||
|
|
a9ccb25cae | ||
|
|
6d32f4f823 | ||
|
|
22f9e7e272 | ||
|
|
e73173888c | ||
|
|
faf8afdd65 | ||
|
|
f2fc0ca891 | ||
|
|
e7346cb28a | ||
|
|
bbc99df84d | ||
|
|
cf0367e069 | ||
|
|
1f1b1b2b01 | ||
|
|
a6841935cc | ||
|
|
96039a47aa | ||
|
|
2d30974f85 | ||
|
|
c5949be319 | ||
|
|
f21833b641 | ||
|
|
35cb12b2f2 | ||
|
|
4efd34f78b | ||
|
|
d35161d5d1 | ||
|
|
fd73225688 | ||
|
|
b7b7f15420 | ||
|
|
b910eb741a | ||
|
|
fec83417f5 | ||
|
|
b3f2c3f8a9 | ||
|
|
1eaf773206 | ||
|
|
38222328c2 | ||
|
|
dc04096587 | ||
|
|
52182d3b4e | ||
|
|
c59f3f3683 | ||
|
|
348b4bcf98 | ||
|
|
f811704e5e | ||
|
|
c2867b6fc8 | ||
|
|
972a5bb357 | ||
|
|
eb73c5b08f | ||
|
|
53607bf019 | ||
|
|
21216b642c | ||
|
|
39c284682a | ||
|
|
5d367195ed | ||
|
|
b38ed83289 | ||
|
|
22ae76c2ce | ||
|
|
1ff85fef2f | ||
|
|
160ec69391 | ||
|
|
a97e5fb762 | ||
|
|
98214786d8 | ||
|
|
01e6f2d6b8 | ||
|
|
9a92276bd0 | ||
|
|
d491a76c43 | ||
|
|
9592f9333a | ||
|
|
fda4b68c56 | ||
|
|
317b6162ea | ||
|
|
5dc6f3be30 | ||
|
|
a999cc8948 | ||
|
|
ef700d9d85 | ||
|
|
bd6e540d04 | ||
|
|
68e0f9e35a | ||
|
|
9e3fcf2e09 | ||
|
|
34019e65ba | ||
|
|
df3fb81b6a | ||
|
|
4fabb49ed3 | ||
|
|
7db5a1305c | ||
|
|
14b5cff081 | ||
|
|
89bb47e53e | ||
|
|
14b54e7790 | ||
|
|
b0044d6f0a | ||
|
|
c0f1a2e1c8 | ||
|
|
50d95f1870 | ||
|
|
e4187a6d4c | ||
|
|
b5a639936b | ||
|
|
4ca6716074 | ||
|
|
1937508781 | ||
|
|
37be5104b2 | ||
|
|
976f0b4e34 | ||
|
|
ab1b2a8a96 | ||
|
|
2081d13b81 | ||
|
|
164b7858f7 | ||
|
|
fb528adeb9 | ||
|
|
4aecfa43ab | ||
|
|
6bc8822e04 | ||
|
|
095e99bc0a | ||
|
|
623a79da54 | ||
|
|
9e9d57be8e | ||
|
|
cbe77b3b1f | ||
|
|
aff9a999b0 | ||
|
|
588f843e0c | ||
|
|
0957ef2804 | ||
|
|
c571bfb03b | ||
|
|
d7445dabb2 | ||
|
|
1dac50f70a | ||
|
|
3a04893caa | ||
|
|
83c292de6b | ||
|
|
a2e18ee7df | ||
|
|
84c455f557 | ||
|
|
22981292cc | ||
|
|
4d0d84c44d | ||
|
|
b9d5fd8e68 | ||
|
|
826d560b84 | ||
|
|
5b47b67c83 | ||
|
|
0561ecba3f | ||
|
|
c9631268bd | ||
|
|
11510bd8b4 | ||
|
|
a58694a04d | ||
|
|
9667a73b05 | ||
|
|
24b57fb269 | ||
|
|
cf1dc459a2 | ||
|
|
306c4c542b | ||
|
|
61bb98460d | ||
|
|
53116df6a0 | ||
|
|
29c70d26c7 | ||
|
|
ee40a2265a | ||
|
|
167df98182 | ||
|
|
904ceb8f09 | ||
|
|
307ba2fe61 | ||
|
|
87b615999d | ||
|
|
1e8ab7f983 | ||
|
|
2293993865 | ||
|
|
69bf08729d | ||
|
|
4fa04a4b6e | ||
|
|
0572480239 | ||
|
|
092ecb407d | ||
|
|
e63886b402 | ||
|
|
4b18b6c4f1 | ||
|
|
678c17c80f | ||
|
|
076cba0c85 | ||
|
|
f9beb589db | ||
|
|
3f439a2aa0 | ||
|
|
b8927722aa | ||
|
|
a0945ecc84 | ||
|
|
e51bdc3647 | ||
|
|
a6b87183e1 | ||
|
|
a491077fca | ||
|
|
2a369d0532 | ||
|
|
e83239af24 | ||
|
|
34a4664499 | ||
|
|
1606c61745 | ||
|
|
ade3c45be5 | ||
|
|
7efda1ba42 | ||
|
|
25c9890fe3 | ||
|
|
baf8c71909 | ||
|
|
369d75af26 | ||
|
|
cf66eb8853 | ||
|
|
f714c9211d | ||
|
|
2d34db53fe | ||
|
|
a7f0065e85 | ||
|
|
8fdb800dea | ||
|
|
a747c5baba | ||
|
|
b4238e06b3 | ||
|
|
2d2e288f5c | ||
|
|
1326716b0b | ||
|
|
a6254b4308 | ||
|
|
f1e2e9561c | ||
|
|
1d5f3bbecd | ||
|
|
a435cb8a20 | ||
|
|
d3d0c59a80 | ||
|
|
f947c97b83 | ||
|
|
5d53c08fd7 | ||
|
|
cb52e7047c | ||
|
|
23a7d22062 | ||
|
|
af7cf437a3 | ||
|
|
adfa2d9c58 | ||
|
|
62d6092956 | ||
|
|
84bd33bfd6 | ||
|
|
9b13f4cea7 | ||
|
|
e2614a9832 | ||
|
|
d1ecb16823 | ||
|
|
e21d8fd836 | ||
|
|
2ffac9e6ca | ||
|
|
9de5181aaa | ||
|
|
e35477aecc | ||
|
|
9930241970 | ||
|
|
494a8da3f7 | ||
|
|
aa25c4735e | ||
|
|
1d260f65bc | ||
|
|
3c6cbe1638 | ||
|
|
fa63a99ae3 | ||
|
|
da1c22f525 | ||
|
|
70bbb630cc | ||
|
|
44814071c8 | ||
|
|
d3a8f22bfb | ||
|
|
43b31cb814 | ||
|
|
40d5972533 | ||
|
|
26329aa75f | ||
|
|
4b698f48fa | ||
|
|
786ebf04d4 | ||
|
|
9fbfcca1c2 | ||
|
|
daf11c8d1a | ||
|
|
28809ff783 | ||
|
|
48dbdeb5c6 | ||
|
|
09bba44229 | ||
|
|
6e8207cb56 | ||
|
|
557254f74a | ||
|
|
3a72ddf3d7 | ||
|
|
043892d23b | ||
|
|
0490604277 | ||
|
|
584a6e5911 | ||
|
|
049a0337b2 | ||
|
|
a42cc5df37 | ||
|
|
4d29933271 | ||
|
|
00ba7af473 | ||
|
|
f2a177bc79 | ||
|
|
04aff76ea4 | ||
|
|
6ff1c07c42 | ||
|
|
c23d0d29dd | ||
|
|
e8d08f8c43 | ||
|
|
9b6f310d4f | ||
|
|
f281396b6f | ||
|
|
95ebf251a2 | ||
|
|
a14163bfca | ||
|
|
c64d4e5cb5 | ||
|
|
9bd70df8bf | ||
|
|
8f68cc6abc | ||
|
|
59709c3ddf | ||
|
|
405e50effb | ||
|
|
631809af3c | ||
|
|
90f95c2963 | ||
|
|
c252d2d554 | ||
|
|
fdb1697ead | ||
|
|
045784f1f9 | ||
|
|
ef13f128ed | ||
|
|
ef46ac9d0e | ||
|
|
b430e4929b | ||
|
|
831fe185e3 | ||
|
|
fdb9c08895 | ||
|
|
abe0a37aca | ||
|
|
9f86286e57 | ||
|
|
9ada74d70b | ||
|
|
855f59b407 | ||
|
|
edcdbcc577 | ||
|
|
a24daad15e | ||
|
|
203f5418fa | ||
|
|
30c37a1fa4 | ||
|
|
863bfb5cf4 | ||
|
|
f8bc0835d0 | ||
|
|
bef32197da | ||
|
|
1b08627c44 | ||
|
|
501cf8869d | ||
|
|
5d272ce420 | ||
|
|
e15762c725 | ||
|
|
a8f54306aa | ||
|
|
664412553d | ||
|
|
b512a35e3d | ||
|
|
6e3268ca17 | ||
|
|
03a2bcf37e | ||
|
|
6f65d127aa | ||
|
|
1034c44ee3 | ||
|
|
4ec6fe308c | ||
|
|
01b05b0bda | ||
|
|
d377fc409f | ||
|
|
c2c0c7e292 | ||
|
|
8609a6acb7 | ||
|
|
7142567848 | ||
|
|
58c93576b2 | ||
|
|
5a4972f940 | ||
|
|
64f94a11cd | ||
|
|
45745ed381 | ||
|
|
6834b664fd | ||
|
|
f03fbbfafe | ||
|
|
ef52f691cf | ||
|
|
273fdfb082 | ||
|
|
1de8c6998a | ||
|
|
cc21ff1863 | ||
|
|
ea0f4d091a | ||
|
|
4a43eabed2 | ||
|
|
9d18de1110 | ||
|
|
87984cdaa8 | ||
|
|
ab5d8ce28a | ||
|
|
118af53cc8 | ||
|
|
d7564d716f | ||
|
|
69d8523b85 | ||
|
|
535cf3b3ba | ||
|
|
ddaffd166a | ||
|
|
7bec8e42ce | ||
|
|
5e3f538674 | ||
|
|
530ccca1e4 | ||
|
|
f1a7cfb48c | ||
|
|
6b9fec8ef7 | ||
|
|
322311bda3 | ||
|
|
643db859a3 | ||
|
|
e374a203ff | ||
|
|
21658b19bc | ||
|
|
d6b439f097 | ||
|
|
74c6be70f7 | ||
|
|
8dd99376fd | ||
|
|
653bf17535 | ||
|
|
4b100d0073 | ||
|
|
5a0d267770 | ||
|
|
adb0442da5 | ||
|
|
674735ca65 | ||
|
|
ae31a360e9 | ||
|
|
2188e9000e | ||
|
|
1623d208f7 | ||
|
|
18e9c91d69 | ||
|
|
2dd0850854 | ||
|
|
65f6e436f3 | ||
|
|
e07b15cd00 | ||
|
|
7de16ec735 | ||
|
|
f18d4f1915 | ||
|
|
855393e137 | ||
|
|
7488330980 | ||
|
|
3ee3e1da5c | ||
|
|
ce48913e43 | ||
|
|
c9faec6c37 | ||
|
|
79041e5f40 | ||
|
|
5d03bdf561 | ||
|
|
8359d2191a | ||
|
|
2f0874531e | ||
|
|
b43cdabd46 | ||
|
|
4aee3477a5 | ||
|
|
2f027e5fa1 | ||
|
|
b6df3aaf38 | ||
|
|
dace5d545a | ||
|
|
0f07854917 | ||
|
|
8efaec8c6d | ||
|
|
ed5e388e7d | ||
|
|
11d1f3f1ce | ||
|
|
0b5d69bf28 | ||
|
|
636e926ece | ||
|
|
c57248363e | ||
|
|
4f88a2e554 | ||
|
|
4b13c38c02 | ||
|
|
c826822ea5 | ||
|
|
ed204b9111 | ||
|
|
2f05508385 | ||
|
|
1108b33f7c | ||
|
|
31e8a8e302 | ||
|
|
0eaa5f458b | ||
|
|
d065e91626 | ||
|
|
eca6f74545 | ||
|
|
39c08c8201 | ||
|
|
01fbe1cf24 | ||
|
|
5d5d82b3d6 | ||
|
|
d213e91260 | ||
|
|
434cc3836a | ||
|
|
3f3036c8cc | ||
|
|
51a98c8e9f | ||
|
|
726e7c3ef1 | ||
|
|
cc7e931682 | ||
|
|
8c35ebdb2c | ||
|
|
00a4561a4b | ||
|
|
181a5207e9 | ||
|
|
4814a552c7 | ||
|
|
46b84fc6a8 | ||
|
|
bc92ae8319 | ||
|
|
d5e857f994 | ||
|
|
bed41badbc | ||
|
|
c4e8692564 | ||
|
|
0bef366685 | ||
|
|
dc072e21af | ||
|
|
fafb90d2d2 | ||
|
|
7222716957 | ||
|
|
ee172e50ed | ||
|
|
f6dc3dfc74 | ||
|
|
80b44941d0 | ||
|
|
73ecb64a9d | ||
|
|
a8502b63b2 | ||
|
|
f120d12939 | ||
|
|
81abf8925b | ||
|
|
dc9aceb1cf | ||
|
|
e70685a166 | ||
|
|
850f322dff | ||
|
|
50d6e0dc1d | ||
|
|
48bf133e46 | ||
|
|
002f024926 | ||
|
|
d86c0a8ba8 | ||
|
|
7603ea0371 | ||
|
|
c97ffbb99e | ||
|
|
dee74fbe03 | ||
|
|
4cf3a53b4f | ||
|
|
2b72e5ea0b | ||
|
|
76c3de4ace | ||
|
|
a89fa7afb0 | ||
|
|
424c388ca3 | ||
|
|
5f1859fff5 | ||
|
|
5af552721c | ||
|
|
3818cbc7cb | ||
|
|
31e5a695d7 | ||
|
|
b7470581a9 | ||
|
|
2a36c6ea6e | ||
|
|
99fefbfb44 | ||
|
|
f045f4eb38 | ||
|
|
2f130502cc | ||
|
|
4b0f123313 | ||
|
|
f69edff927 | ||
|
|
246535f3f1 | ||
|
|
efaab0c83b | ||
|
|
669660a49c | ||
|
|
3651e58631 | ||
|
|
2ba90838e4 | ||
|
|
72ded26393 | ||
|
|
111e0914b5 | ||
|
|
2bfa24e72c | ||
|
|
bf61dd030a | ||
|
|
592143137f | ||
|
|
999ad82e22 | ||
|
|
1f95368168 | ||
|
|
bce8d05170 | ||
|
|
7d72d4cc9c | ||
|
|
3285b20d98 | ||
|
|
8cd92cc8c3 | ||
|
|
6a993f765e | ||
|
|
565fb896bd | ||
|
|
d6cff70840 | ||
|
|
56f57e2ad6 | ||
|
|
961f6059b7 | ||
|
|
2f34b6271e | ||
|
|
2c97bce3c0 | ||
|
|
ed8cad319d | ||
|
|
8e39fe29ba | ||
|
|
1ee72b03b4 | ||
|
|
92ad2b4a0d | ||
|
|
61d500142c | ||
|
|
cf361902c2 | ||
|
|
07d6293173 | ||
|
|
c446321173 | ||
|
|
17d59c1e15 | ||
|
|
097f4c90fd | ||
|
|
87670bfe52 | ||
|
|
f5ba140830 | ||
|
|
854051b42f | ||
|
|
076fc443d6 | ||
|
|
43a5e10d72 | ||
|
|
2f27e82a7b | ||
|
|
d2871d7bc9 | ||
|
|
4171eafeb9 | ||
|
|
90a1ce0958 | ||
|
|
3a75c5c306 | ||
|
|
ebc5b6409b | ||
|
|
e8314bfced | ||
|
|
06abcc0905 | ||
|
|
168af9eb81 | ||
|
|
f57a8862f6 | ||
|
|
e6cbb52111 | ||
|
|
238006dbb2 | ||
|
|
3366f6b10c | ||
|
|
8706ecab72 | ||
|
|
2ab8e2f981 | ||
|
|
958a220163 | ||
|
|
62a5b55303 | ||
|
|
4ed0cea596 | ||
|
|
c7676f9e63 | ||
|
|
8cbd780479 | ||
|
|
c999b53691 | ||
|
|
af4f1a5533 | ||
|
|
2904ba74dc | ||
|
|
d97a3af401 | ||
|
|
c8d2d7ce87 | ||
|
|
b0e5224baa | ||
|
|
34546e5528 | ||
|
|
7cb3a4981e | ||
|
|
85a11e4195 | ||
|
|
8d82b68ab5 | ||
|
|
c778c98d64 | ||
|
|
44cbb43472 | ||
|
|
107ccce58a | ||
|
|
8fb4599a06 | ||
|
|
db7522970f | ||
|
|
0b8253b8e4 | ||
|
|
8f38a35479 | ||
|
|
5b5c805bb0 | ||
|
|
6fcb411d80 | ||
|
|
d8ca70b5cf | ||
|
|
7a6cdc9460 | ||
|
|
eb0d034a46 | ||
|
|
de3c9e7337 | ||
|
|
bb86520f43 | ||
|
|
2e3626069e | ||
|
|
b395d71a3c | ||
|
|
f7843cb5b2 | ||
|
|
9b0e042add | ||
|
|
5bde8145a2 | ||
|
|
99ddaec57a | ||
|
|
eb46ac3d39 | ||
|
|
8aead373d1 | ||
|
|
c79d79e4ce | ||
|
|
090dc09d39 | ||
|
|
9432deb0b9 | ||
|
|
cd1dd82978 | ||
|
|
f9fafb844b | ||
|
|
bdac4492ac | ||
|
|
78e1f711c4 | ||
|
|
9cfc88b96f | ||
|
|
c53ca529a8 | ||
|
|
b8e5bae524 | ||
|
|
904e1f9dc7 | ||
|
|
655aedc478 | ||
|
|
1d69805fac | ||
|
|
bf75ef1c36 | ||
|
|
e1c9cdfcb9 | ||
|
|
2aa5d94cea | ||
|
|
c49d7f36f5 | ||
|
|
598c28bb9e | ||
|
|
f56c126ef4 | ||
|
|
02686bd919 | ||
|
|
37d880af2d | ||
|
|
04f6835892 | ||
|
|
21e72a2e8f | ||
|
|
497d9b664d | ||
|
|
09b1c24b61 | ||
|
|
85a179bf81 | ||
|
|
0238a9ae07 | ||
|
|
26946b8e70 | ||
|
|
97920a8bb0 | ||
|
|
48bb71d615 | ||
|
|
2244a1cbda | ||
|
|
0094818d73 | ||
|
|
df23beb9bb | ||
|
|
05ac8c89e5 | ||
|
|
3d5a454fe3 | ||
|
|
a88797a92b | ||
|
|
25ed97cc53 | ||
|
|
2083e0a2d1 | ||
|
|
b6c38aeddc | ||
|
|
b2abecdd7d | ||
|
|
d03ec33f70 | ||
|
|
9ac4e57c7c | ||
|
|
c2e82d8943 | ||
|
|
85b4594ad7 | ||
|
|
52bd01315c | ||
|
|
71f9180c5d | ||
|
|
b03f275ead | ||
|
|
ebbd1355c6 | ||
|
|
a28c5382b4 | ||
|
|
44e7298d86 | ||
|
|
934be31248 | ||
|
|
767eb4e7a7 | ||
|
|
c10a759478 | ||
|
|
e9cc5f1518 | ||
|
|
ee37ea5555 | ||
|
|
8800b9b887 | ||
|
|
b9368d912c | ||
|
|
a75cfd65f3 | ||
|
|
bd7f1f58bf | ||
|
|
ad7229039f | ||
|
|
9ddfad0fbe | ||
|
|
10a7f8a1da | ||
|
|
0a1f981bb1 | ||
|
|
9053cf8700 | ||
|
|
909b33a897 | ||
|
|
b96a34a1f4 | ||
|
|
11e21bbdda | ||
|
|
e18b6da679 | ||
|
|
dc135f22a9 | ||
|
|
fc88f6e38d | ||
|
|
fa5be3a73a | ||
|
|
26c20d34a3 |
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -7,9 +7,9 @@ body:
|
||||
attributes:
|
||||
label: Checklist before submitting an issue
|
||||
options:
|
||||
- label: I have searched through the existing [closed and open issues](https://github.com/elkowar/eww/issues?q=is%3Aissue) for eww and made sure this is not a duplicate
|
||||
- label: I have searched through the existing [closed and open issues](https://github.com/Ewwii-sh/ewwii/issues?q=is%3Aissue) for ewwii and made sure this is not a duplicate
|
||||
required: true
|
||||
- label: I have specifically verified that this bug is not a common [user error](https://github.com/elkowar/eww/issues?q=is%3Aissue+label%3Ano-actual-bug+is%3Aclosed)
|
||||
- label: I have specifically verified that this bug is not a common [user error](https://github.com/Ewwii-sh/ewwii/issues?q=is%3Aissue+label%3Ano-actual-bug+is%3Aclosed)
|
||||
required: true
|
||||
- label: I am providing as much relevant information as I am able to in this bug report (Minimal config to reproduce the issue for example, if applicable)
|
||||
required: true
|
||||
@@ -40,6 +40,6 @@ body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Platform and environment"
|
||||
description: "Does this happen on wayland, X11, or on both? What WM/Compositor are you using? Which version of eww are you using? (when using a git version, optimally provide the exact commit ref)."
|
||||
description: "Does this happen on wayland, X11, or on both? What WM/Compositor are you using? Which version of ewwii are you using? (when using a git version, optimally provide the exact commit ref)."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -12,7 +12,7 @@ body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Proposed configuration syntax"
|
||||
description: "If the feature you are requesting would add or change something to the Eww configuration, please provide an example for how the feature could be used."
|
||||
description: "If the feature you are requesting would add or change something to the Ewwii configuration, please provide an example for how the feature could be used."
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
|
||||
1
.github/pull_request_template.md
vendored
1
.github/pull_request_template.md
vendored
@@ -26,5 +26,4 @@ Please make sure you can check all the boxes that apply to this PR.
|
||||
|
||||
- [ ] All widgets I've added are correctly documented.
|
||||
- [ ] I added my changes to CHANGELOG.md, if appropriate.
|
||||
- [ ] The documentation in the `docs/content/main` directory has been adjusted to reflect my changes.
|
||||
- [ ] I used `cargo fmt` to automatically format all code before committing
|
||||
|
||||
73
.github/workflows/build.yml
vendored
73
.github/workflows/build.yml
vendored
@@ -1,45 +1,56 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev libdbusmenu-gtk3-dev
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: archlinux:latest
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pacman -Syu --noconfirm
|
||||
pacman -S --noconfirm base-devel gtk4 gtk4-layer-shell pkgconf git
|
||||
|
||||
- name: Setup rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy,rustfmt
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Load rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Setup rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy,rustfmt
|
||||
|
||||
- name: Setup problem matchers
|
||||
uses: r7kamura/rust-problem-matchers@v1
|
||||
- name: Load rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Check formatting
|
||||
run: cargo fmt -- --check
|
||||
- name: Check with default features
|
||||
run: cargo check
|
||||
- name: Setup problem matchers
|
||||
uses: r7kamura/rust-problem-matchers@v1
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test
|
||||
- name: Check formatting
|
||||
run: cargo fmt -- --check
|
||||
|
||||
- name: Check x11 only
|
||||
run: cargo check --no-default-features --features=x11
|
||||
- name: Check wayland only
|
||||
run: cargo check --no-default-features --features=wayland
|
||||
- name: Check no-backend
|
||||
run: cargo check --no-default-features
|
||||
- name: Check with default features
|
||||
run: cargo check
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test
|
||||
|
||||
- name: Check x11 only
|
||||
run: cargo check --no-default-features --features=x11
|
||||
|
||||
- name: Check wayland only
|
||||
run: cargo check --no-default-features --features=wayland
|
||||
|
||||
- name: Check no-backend
|
||||
run: cargo check --no-default-features
|
||||
|
||||
93
.github/workflows/gh-pages.yml
vendored
93
.github/workflows/gh-pages.yml
vendored
@@ -1,93 +0,0 @@
|
||||
name: Build and deploy Github pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "docs/**"
|
||||
- ".github/workflows/**"
|
||||
- "tools/generate-rhai-docs/**"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build mdBook
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Checkout code
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Cache apt package lists
|
||||
- name: Cache apt-get package lists
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
/var/cache/apt/archives
|
||||
/var/lib/apt/lists
|
||||
key: ${{ runner.os }}-apt-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-apt-
|
||||
|
||||
# Install necessary dependencies for glib-sys and gobject-sys
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
pkg-config \
|
||||
libcairo2-dev \
|
||||
libglib2.0-dev \
|
||||
libgirepository1.0-dev \
|
||||
libpango1.0-dev \
|
||||
libatk1.0-dev \
|
||||
libgtk-3-dev
|
||||
|
||||
# Cache Rust toolchain
|
||||
- name: Cache Rust toolchain
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cargo
|
||||
key: ${{ runner.os }}-rust-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-rust-
|
||||
|
||||
# Install Rust
|
||||
- name: Install Rust
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
source $HOME/.cargo/env
|
||||
|
||||
# Cache Cargo registry and git directory
|
||||
- name: Cache Cargo registry and git
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-
|
||||
|
||||
# Generate module docs
|
||||
- name: Generate module docs
|
||||
run: cargo run --release -p generate-rhai-docs
|
||||
|
||||
# Build mdBook
|
||||
- name: Build mdBook page
|
||||
uses: peaceiris/actions-mdbook@v1
|
||||
with:
|
||||
mdbook-version: "0.4.52"
|
||||
|
||||
- name: Run mdBook build
|
||||
run: mdbook build docs
|
||||
|
||||
# Deploy to GitHub Pages
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./docs/book/
|
||||
167
CHANGELOG.md
167
CHANGELOG.md
@@ -5,6 +5,173 @@ All notable changes to `ewwii` are documented here.
|
||||
This changelog follows the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format,
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
### Added
|
||||
|
||||
- `gtk_ui` function for loading .ui files.
|
||||
- `widget-control` (`wc` in short) command for controlling widgets.
|
||||
- `placeholder` property to input widget.
|
||||
- `transition_duration` property to stack widget.
|
||||
- `widget_control` utility function for dynamic widget handling.
|
||||
- `text` and `show_text` property to progressbar widget.
|
||||
- `content_fit` property to image widget.
|
||||
- `can_shrink` property to image widget.
|
||||
- `mutations` property to localsignal.
|
||||
- `eval_ignore` property to all widgets.
|
||||
- Touch support to scale widget.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `clockwise` property not working on circular_progress.
|
||||
|
||||
### Removed
|
||||
|
||||
- icon widget.
|
||||
|
||||
## [0.3.1] - 2025-11-01
|
||||
|
||||
## Fixed
|
||||
|
||||
- Circular progress bar not updating dynamically.
|
||||
- LocalSignal values not getting transformed to suite property type.
|
||||
- LocalBind not finding properties of range subclasses.
|
||||
|
||||
## [0.3.0] - 2025-11-01
|
||||
|
||||
### Added
|
||||
|
||||
- `localsignal` signal for fast and cheap property update.
|
||||
- `localbind` utility for binding `localsignal` to a widget property.
|
||||
- `onkeypress` property to eventbox.
|
||||
- `onkeyrelease` property to eventbox.
|
||||
- Selection of dash as shell if it installed.
|
||||
- `--with-plugin` flag for `daemon` command.
|
||||
- `register_function` API in ewwii_plugin_api for registering functions that rhai can call to.
|
||||
- `slib` rhai module for calling functions registered via `register_function`.
|
||||
- `orientation` property to eventbox.
|
||||
- `spacing` property to eventbox.
|
||||
- `space_evenly` property to eventbox.
|
||||
- An advanced widget named `flowbox`.
|
||||
- `focusable` property to all widget.
|
||||
- `widget_name` property to all widget.
|
||||
- `lifetime` flag for update command.
|
||||
- `circular-progress` widget back.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Old widget creeping into new ones after hot reload.
|
||||
- Ewwii defaulting to default gtk4 theme. #9
|
||||
- Few issues with eventbox drop target.
|
||||
- Poll/Listen variables not exposed in other modules on first launch.
|
||||
- Not able to define poll/listen variables in other files.
|
||||
- Overlay and Tooltip widgets not being reactive.
|
||||
|
||||
## [0.3.0-beta] - 2025-10-11
|
||||
|
||||
### Added
|
||||
|
||||
- Support for binary level plugins.
|
||||
- `set-plugin` command to load shared libraries (i.e plugins).
|
||||
- `ewwii_plugin_api` crate for building ewwii plugins easily.
|
||||
|
||||
### Changed
|
||||
|
||||
- The name of `slider` widget to `scale`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `focusable` property not working issue.
|
||||
- Poll/Listen variable scope not accessable to all modules.
|
||||
|
||||
## [0.3.0-alpha] - 2025-10-04
|
||||
|
||||
### Added
|
||||
|
||||
- GTK4 support.
|
||||
- `can_target` boolean property for all widgets.
|
||||
- `icon` widget which always preserves a scaling of 1:1.
|
||||
- Full native wayland compatibility (x11 compatibility has decreased).
|
||||
|
||||
### Changed
|
||||
|
||||
- The way dynamic updates are handled.
|
||||
- Window positioning logic.
|
||||
- X11 communication and x11 based window handling.
|
||||
- Daemon GTK main loop.
|
||||
- Application of sticky and stacking propery of window on X11.
|
||||
- The full implementation of eventbox widget.
|
||||
- GTK controller/signal handling.
|
||||
- Initialization logic of window.
|
||||
- Overlay widget dynamic post-creation re-creation logic (bugs are expected).
|
||||
|
||||
### Removed
|
||||
|
||||
- `show_details` property of calendar.
|
||||
- `angle` property from label.
|
||||
- `icon_size` property from image widget.
|
||||
- `same_size` property from stack widget (box is a replacement).
|
||||
- `icon_name` from `image` widget as a result of the introduction of `icon` widget.
|
||||
- `transform`, `graph`, and `circular_progress` widget (temporarily).
|
||||
- Application of css class on the window.
|
||||
- Legacy GTK3 related code.
|
||||
- Centerbox widget.
|
||||
|
||||
## [0.2.0] - 2025-09-29
|
||||
|
||||
### Added
|
||||
|
||||
- `state` command to print the current poll/listen variable state.
|
||||
- `m` as another duration unit for minute.
|
||||
- `std::regex` library for regex matching.
|
||||
- `engine-override` command which can be used to change engine settings of a configuration.
|
||||
- `force_normal` property for ewwii windows. It allows user to create normal windows on wayland.
|
||||
- Better error support by migrating to `rhai_trace` v0.3.0.
|
||||
- File path indicator in rhai errors.
|
||||
|
||||
### Fixed
|
||||
|
||||
- The logs going to `eww_{}.log` instead of `ewwii_{}.log`.
|
||||
- Logs not truncating if it is over 100MB and not deleting if over 7 days old.
|
||||
- Ewwii crashing on invalid duration property.
|
||||
- The module resolver throwing error at `import` defenition.
|
||||
- Fixed commands sending error with success status.
|
||||
|
||||
### Removed
|
||||
|
||||
- Legacy `true`/`false` support for `focusable` window property.
|
||||
- `$INPUT_VAL` variable injected in commands ran by input widget.
|
||||
- Many dependencies and code for faster build and lesser binary size.
|
||||
- `monitor` library as a step towards GTK4.
|
||||
|
||||
## [0.1.4] - 2025-09-18
|
||||
|
||||
### Added
|
||||
|
||||
- `--preserve` flag to the `update` command which preserves the new updates.
|
||||
|
||||
## [0.1.3] - 2025-09-17
|
||||
|
||||
### Changed
|
||||
|
||||
- `update` command so that it preserves current widget state.
|
||||
- `--inject-vars` argument of update to just `--inject` (or `-i` in short).
|
||||
|
||||
### Fixed
|
||||
|
||||
- `image_width` and `image_height` not working for image widget.
|
||||
|
||||
## [0.1.2] - 2025-09-13
|
||||
|
||||
### Added
|
||||
|
||||
- "Parent-death signal is not supported" warning on macOS.
|
||||
- Error logging on parent-death signal fail.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Code not compiling for FreeBSD.
|
||||
|
||||
## [0.1.1] - 2025-09-07
|
||||
|
||||
### Added
|
||||
|
||||
796
Cargo.lock
generated
796
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
24
Cargo.toml
24
Cargo.toml
@@ -1,19 +1,18 @@
|
||||
[workspace]
|
||||
members = ["crates/*", "tools/*"]
|
||||
members = ["crates/*", "tools/*", "proc_macros/*"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
|
||||
shared_utils = { version = "0.1.0", path = "crates/shared_utils" }
|
||||
rhai_impl = { version = "0.1.0", path = "crates/rhai_impl" }
|
||||
scan_prop_proc = { version = "0.1.0", path = "proc_macros/scan_prop_proc" }
|
||||
ewwii_plugin_api = { version = "0.7.0", path = "crates/ewwii_plugin_api" }
|
||||
|
||||
anyhow = "1.0.86"
|
||||
ahash = "0.8.12"
|
||||
bincode = "1.3.3"
|
||||
bytesize = "2.0.1"
|
||||
cached = "0.53.1"
|
||||
chrono = "0.4.38"
|
||||
chrono-tz = "0.10.0"
|
||||
clap = { version = "4.5.1", features = ["derive"] }
|
||||
clap_complete = "4.5.12"
|
||||
codespan-reporting = "0.11"
|
||||
@@ -27,14 +26,8 @@ derive_more = { version = "1", features = [
|
||||
extend = "1.2"
|
||||
futures = "0.3.30"
|
||||
grass = "0.13.4"
|
||||
gtk = "0.18.1"
|
||||
insta = "1.7"
|
||||
gtk4 = { version = "0.10.1", features = ["v4_8"] }
|
||||
itertools = "0.13.0"
|
||||
jaq-core = "1.5.1"
|
||||
jaq-parse = "1.0.3"
|
||||
jaq-std = "1.6.0"
|
||||
jaq-interpret = "1.5.0"
|
||||
jaq-syn = "1.6.0"
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
nix = "0.29.0"
|
||||
@@ -42,20 +35,21 @@ notify = "6.1.1"
|
||||
once_cell = "1.19"
|
||||
pretty_assertions = "1.4.0"
|
||||
pretty_env_logger = "0.5.0"
|
||||
ref-cast = "1.0.22"
|
||||
regex = "1.10.5"
|
||||
rhai = { version = "1.22.2", features = ["internals"] }
|
||||
rhai = "1.23.6"
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
simple-signal = "1.1"
|
||||
smart-default = "0.7.1"
|
||||
static_assertions = "1.1.0"
|
||||
strsim = "0.11"
|
||||
strum = { version = "0.26", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.39.2", features = ["full"] }
|
||||
unescape = "0.1"
|
||||
wait-timeout = "0.2"
|
||||
syn = "2.0.107"
|
||||
quote = "1.0.41"
|
||||
proc-macro2 = "1.0.101"
|
||||
shell-words = "1.1.0"
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
|
||||
26
README.md
26
README.md
@@ -1,25 +1,31 @@
|
||||
[](https://deps.rs/repo/github/byson94/ewwii)
|
||||
[](https://ewwii-sh.github.io/docs)
|
||||
|
||||
# Ewwii
|
||||
|
||||
<img src="./.github/EwwiiLogo.png" height="100" align="left"/>
|
||||
|
||||
Elkowars Wacky Widgets Imporved Interface is a fork of Elkowars Wacky Widgets which is a standalone widget system made in Rust that allows you to implement your own, custom widgets in any window manager.
|
||||
Elkowars Wacky Widgets Improved Interface is a fork of Elkowars Wacky Widgets which is a standalone widget system made in Rust that allows you to implement your own, custom widgets in any window manager.
|
||||
|
||||
## Examples
|
||||
|
||||
All examples are in the [examples directory](./examples/).
|
||||
Examples of projects powered by ewwii.
|
||||
|
||||
- A basic bar [CLICK TO SEE](./examples/ewwii-bar) <br>
|
||||

|
||||
| Project | Preview |
|
||||
|---------|---------|
|
||||
| **Basic Bar**<br>[- View Example](./examples/ewwii-bar) | [](./examples/ewwii-bar) |
|
||||
| **Data Structures**<br>[- View Example](./examples/data-structures) | [](./examples/data-structures) |
|
||||
| **Wi-Fi GUI Template**<br>[- View on GitHub](https://github.com/Ewwii-sh/ewifi_gui_template) |  |
|
||||
| **Obsidian Bar Template**<br>[- View on GitHub](https://github.com/Ewwii-sh/obsidian-bar) | [](https://github.com/Ewwii-sh/obsidian-bar) |
|
||||
| **Binary Dots by [@BinaryHarbinger](https://github.com/BinaryHarbinger)**<br>[- View on GitHub](https://github.com/BinaryHarbinger/binarydots/) | [](https://github.com/BinaryHarbinger/binarydots)
|
||||
| **Astatine Dots (Linux Rice with Ewwii)**<br>[- View on GitHub](https://github.com/Ewwii-sh/astatine-dots) | [](https://github.com/Ewwii-sh/astatine-dots) |
|
||||
|
||||
- Data structures [CLICK TO SEE](./examples/data-structures) <br>
|
||||

|
||||
## Features
|
||||
|
||||
## Templates
|
||||
|
||||
- A wifi gui template [CLICK TO SEE](https://github.com/Ewwii-sh/ewifi_gui_template) <br>
|
||||
<img src="https://raw.githubusercontent.com/Ewwii-sh/ewwii/main/docs/src/images/wifi_manager_template.png" width="400" />
|
||||
- Powered by Gtk4
|
||||
- Supports Hot reload
|
||||
- Extensibility via plugins and rhai modules
|
||||
- X11 + Wayland support
|
||||
|
||||
## Contribewwtiing
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ewwii"
|
||||
version = "0.1.0"
|
||||
version = "0.4.0"
|
||||
authors = ["byson94 <byson94wastaken@gmail.com>"]
|
||||
description = "Widgets for everyone made better!"
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -11,17 +11,17 @@ edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["x11", "wayland"]
|
||||
x11 = ["gdkx11", "x11rb"]
|
||||
wayland = ["gtk-layer-shell"]
|
||||
x11 = ["gdk4-x11", "x11rb"]
|
||||
wayland = ["gtk4-layer-shell"]
|
||||
|
||||
[dependencies]
|
||||
shared_utils.workspace = true
|
||||
rhai_impl.workspace = true
|
||||
ewwii_plugin_api.workspace = true
|
||||
|
||||
gtk-layer-shell = { version = "0.8.1", optional = true, features=["v0_6"] }
|
||||
gdkx11 = { version = "0.18", optional = true }
|
||||
gtk4-layer-shell = { version = "0.6.3", optional = true }
|
||||
gdk4-x11 = { version = "0.10.1", optional = true }
|
||||
x11rb = { version = "0.13.1", features = ["randr"], optional = true }
|
||||
gdk-sys = "0.18.0"
|
||||
|
||||
grass.workspace = true
|
||||
thiserror.workspace = true
|
||||
@@ -35,7 +35,7 @@ derive_more.workspace = true
|
||||
extend.workspace = true
|
||||
futures.workspace = true
|
||||
smart-default.workspace = true
|
||||
gtk.workspace = true
|
||||
gtk4.workspace = true
|
||||
itertools.workspace = true
|
||||
log.workspace = true
|
||||
nix = { workspace = true, features = ["process", "fs", "signal"] }
|
||||
@@ -49,7 +49,10 @@ simple-signal.workspace = true
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
unescape.workspace = true
|
||||
wait-timeout.workspace = true
|
||||
rhai.workspace = true
|
||||
rhai = { workspace = true, features = ["internals"] }
|
||||
shell-words.workspace = true
|
||||
# Plugin loading
|
||||
libloading = "0.8.9"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions.workspace = true
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ use std::process::Stdio;
|
||||
use crate::{
|
||||
daemon_response::DaemonResponse,
|
||||
opts::{self, ActionClientOnly},
|
||||
paths::EwwPaths,
|
||||
paths::EwwiiPaths,
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
use std::{
|
||||
@@ -11,7 +11,7 @@ use std::{
|
||||
os::unix::net::UnixStream,
|
||||
};
|
||||
|
||||
pub fn handle_client_only_action(paths: &EwwPaths, action: ActionClientOnly) -> Result<()> {
|
||||
pub fn handle_client_only_action(paths: &EwwiiPaths, action: ActionClientOnly) -> Result<()> {
|
||||
match action {
|
||||
ActionClientOnly::Logs => {
|
||||
std::process::Command::new("tail")
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::{
|
||||
// ipc_server,
|
||||
// error_handling_ctx,
|
||||
paths::EwwPaths,
|
||||
paths::EwwiiPaths,
|
||||
window::backend_window_options::BackendWindowOptionsDef,
|
||||
};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -13,10 +14,13 @@ use rhai_impl::{ast::WidgetNode, parser::ParseConfig};
|
||||
|
||||
// use tokio::{net::UnixStream, runtime::Runtime, sync::mpsc};
|
||||
|
||||
/// Load an [`EwwiiConfig`] from the config dir of the given [`crate::EwwPaths`],
|
||||
/// Load an [`EwwiiConfig`] from the config dir of the given [`crate::EwwiiPaths`],
|
||||
/// resetting and applying the global YuckFiles object in [`crate::error_handling_ctx`].
|
||||
pub fn read_from_ewwii_paths(eww_paths: &EwwPaths) -> Result<EwwiiConfig> {
|
||||
EwwiiConfig::read_from_dir(eww_paths)
|
||||
pub fn read_from_ewwii_paths(
|
||||
eww_paths: &EwwiiPaths,
|
||||
parser: &mut ParseConfig,
|
||||
) -> Result<EwwiiConfig> {
|
||||
EwwiiConfig::read_from_dir(eww_paths, parser)
|
||||
}
|
||||
|
||||
/// Ewwii configuration structure.
|
||||
@@ -24,7 +28,7 @@ pub fn read_from_ewwii_paths(eww_paths: &EwwPaths) -> Result<EwwiiConfig> {
|
||||
pub struct EwwiiConfig {
|
||||
windows: HashMap<String, WindowDefinition>,
|
||||
root_node: Option<Rc<WidgetNode>>,
|
||||
compiled_ast: Option<Rc<AST>>,
|
||||
compiled_ast: Option<Rc<RefCell<AST>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -36,26 +40,28 @@ pub struct WindowDefinition {
|
||||
}
|
||||
|
||||
impl EwwiiConfig {
|
||||
/// Load an [`EwwiiConfig`] from the config dir of the given [`crate::EwwPaths`], reading the main config file.
|
||||
pub fn read_from_dir(eww_paths: &EwwPaths) -> Result<Self> {
|
||||
/// Load an [`EwwiiConfig`] from the config dir of the given [`crate::EwwiiPaths`], reading the main config file.
|
||||
pub fn read_from_dir(eww_paths: &EwwiiPaths, config_parser: &mut ParseConfig) -> Result<Self> {
|
||||
let rhai_path = eww_paths.get_rhai_path();
|
||||
if !rhai_path.exists() {
|
||||
bail!("The configuration file `{}` does not exist", rhai_path.display());
|
||||
}
|
||||
|
||||
// initialize configuration parser
|
||||
let mut config_parser = ParseConfig::new();
|
||||
|
||||
// get code from file
|
||||
let rhai_code = config_parser.code_from_file(&rhai_path)?;
|
||||
|
||||
// Get Option<&str> form of rhai_path
|
||||
let rhai_path_opt_str = rhai_path.to_str();
|
||||
|
||||
// get the iirhai widget tree
|
||||
let compiled_ast = config_parser.compile_code(&rhai_code)?;
|
||||
let compiled_ast =
|
||||
config_parser.compile_code(&rhai_code, rhai_path_opt_str.unwrap_or("<rhai>"))?;
|
||||
let poll_listen_scope = ParseConfig::initial_poll_listen_scope(&rhai_code)?;
|
||||
let config_tree = config_parser.eval_code_with(
|
||||
&rhai_code,
|
||||
Some(poll_listen_scope),
|
||||
Some(&compiled_ast),
|
||||
rhai_path_opt_str,
|
||||
)?;
|
||||
|
||||
let mut window_definitions = HashMap::new();
|
||||
@@ -82,7 +88,7 @@ impl EwwiiConfig {
|
||||
Ok(EwwiiConfig {
|
||||
windows: window_definitions,
|
||||
root_node: Some(Rc::new(config_tree)),
|
||||
compiled_ast: Some(Rc::new(compiled_ast)),
|
||||
compiled_ast: Some(Rc::new(RefCell::new(compiled_ast))),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -104,20 +110,18 @@ impl EwwiiConfig {
|
||||
self.root_node.clone().ok_or_else(|| anyhow::anyhow!("root_node is missing"))
|
||||
}
|
||||
|
||||
pub fn get_windows_root_widget(config_tree: WidgetNode) -> Result<WidgetNode> {
|
||||
if let WidgetNode::Enter(children) = config_tree {
|
||||
for node in children {
|
||||
if let WidgetNode::DefWindow { node: boxed_node, .. } = node {
|
||||
return Ok(*boxed_node);
|
||||
}
|
||||
}
|
||||
bail!("No `DefWindow` found inside `Enter`");
|
||||
} else {
|
||||
bail!("Expected root node to be `Enter`, but got something else.");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_owned_compiled_ast(&self) -> Option<Rc<AST>> {
|
||||
pub fn get_owned_compiled_ast(&self) -> Option<Rc<RefCell<AST>>> {
|
||||
self.compiled_ast.clone()
|
||||
}
|
||||
|
||||
pub fn replace_data(&mut self, new_dat: Self) {
|
||||
if let (Some(old_ast_rc), Some(new_ast_rc)) =
|
||||
(self.compiled_ast.as_ref(), new_dat.compiled_ast.as_ref())
|
||||
{
|
||||
*old_ast_rc.borrow_mut() = new_ast_rc.borrow().clone();
|
||||
}
|
||||
|
||||
self.windows = new_dat.windows;
|
||||
self.root_node = new_dat.root_node;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Types to manage messages that notify the eww client over the result of a command
|
||||
//! Types to manage messages that notify the ewwii client over the result of a command
|
||||
//!
|
||||
//! Communcation between the daemon and eww client happens via IPC.
|
||||
//! Communcation between the daemon and ewwii client happens via IPC.
|
||||
//! If the daemon needs to send messages back to the client as a response to a command (mostly for CLI output),
|
||||
//! this happens via the DaemonResponse types
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
|
||||
use crate::window_initiator::WindowInitiator;
|
||||
use gtk4::Window;
|
||||
|
||||
use gtk::gdk;
|
||||
use gtk4::gdk;
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub use platform_wayland::WaylandBackend;
|
||||
@@ -32,10 +33,15 @@ impl DisplayBackend for NoBackend {
|
||||
fn initialize_window(
|
||||
_window_init: &WindowInitiator,
|
||||
_monitor: gdk::Rectangle,
|
||||
x: i32,
|
||||
y: i32,
|
||||
_x: i32,
|
||||
_y: i32,
|
||||
) -> Option<Window> {
|
||||
Some(Window::new(gtk::WindowType::Toplevel, x, y))
|
||||
// top level
|
||||
let window = Window::new();
|
||||
|
||||
// window.move_(x, y);
|
||||
|
||||
Some(window)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,10 +51,11 @@ mod platform_wayland {
|
||||
use crate::window::backend_window_options::WlWindowFocusable;
|
||||
use crate::window::window_definition::WindowStacking;
|
||||
use crate::window::window_geometry::AnchorAlignment;
|
||||
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
|
||||
use gtk::gdk;
|
||||
use gtk::prelude::*;
|
||||
use gtk_layer_shell::{KeyboardMode, LayerShell};
|
||||
use crate::window_initiator::WindowInitiator;
|
||||
use gtk4::gdk;
|
||||
use gtk4::prelude::*;
|
||||
use gtk4::Window;
|
||||
use gtk4_layer_shell::{KeyboardMode, LayerShell};
|
||||
|
||||
pub struct WaylandBackend;
|
||||
|
||||
@@ -59,33 +66,88 @@ mod platform_wayland {
|
||||
fn initialize_window(
|
||||
window_init: &WindowInitiator,
|
||||
monitor: gdk::Rectangle,
|
||||
x: i32,
|
||||
y: i32,
|
||||
_x: i32,
|
||||
_y: i32,
|
||||
) -> Option<Window> {
|
||||
let window = Window::new(gtk::WindowType::Toplevel, x, y);
|
||||
// Initialising a layer shell surface
|
||||
window.init_layer_shell();
|
||||
// Sets the monitor where the surface is shown
|
||||
if let Some(ident) = window_init.monitor.clone() {
|
||||
let display = gdk::Display::default().expect("could not get default display");
|
||||
if let Some(monitor) = crate::app::get_monitor_from_display(&display, &ident) {
|
||||
window.set_monitor(&monitor);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let window = Window::new();
|
||||
// window.move_(x, y);
|
||||
|
||||
window.set_resizable(window_init.resizable);
|
||||
|
||||
// Sets the layer where the layer shell surface will spawn
|
||||
match window_init.stacking {
|
||||
WindowStacking::Foreground => window.set_layer(gtk_layer_shell::Layer::Top),
|
||||
WindowStacking::Background => window.set_layer(gtk_layer_shell::Layer::Background),
|
||||
WindowStacking::Bottom => window.set_layer(gtk_layer_shell::Layer::Bottom),
|
||||
WindowStacking::Overlay => window.set_layer(gtk_layer_shell::Layer::Overlay),
|
||||
}
|
||||
if !window_init.backend_options.wayland.force_normal {
|
||||
// Initialising a layer shell surface
|
||||
window.init_layer_shell();
|
||||
// Sets the monitor where the surface is shown
|
||||
if let Some(ident) = window_init.monitor.clone() {
|
||||
let display = gdk::Display::default().expect("could not get default display");
|
||||
if let Some(monitor) = crate::app::get_monitor_from_display(&display, &ident) {
|
||||
window.set_monitor(Some(&monitor));
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(namespace) = &window_init.backend_options.wayland.namespace {
|
||||
window.set_namespace(namespace);
|
||||
// Sets the layer where the layer shell surface will spawn
|
||||
match window_init.stacking {
|
||||
WindowStacking::Foreground => window.set_layer(gtk4_layer_shell::Layer::Top),
|
||||
WindowStacking::Background => {
|
||||
window.set_layer(gtk4_layer_shell::Layer::Background)
|
||||
}
|
||||
WindowStacking::Bottom => window.set_layer(gtk4_layer_shell::Layer::Bottom),
|
||||
WindowStacking::Overlay => window.set_layer(gtk4_layer_shell::Layer::Overlay),
|
||||
}
|
||||
|
||||
if let Some(namespace) = &window_init.backend_options.wayland.namespace {
|
||||
window.set_namespace(Some(namespace));
|
||||
}
|
||||
|
||||
if let Some(geometry) = window_init.geometry {
|
||||
// Positioning surface
|
||||
let mut top = false;
|
||||
let mut left = false;
|
||||
let mut right = false;
|
||||
let mut bottom = false;
|
||||
|
||||
match geometry.anchor_point.x {
|
||||
AnchorAlignment::START => left = true,
|
||||
AnchorAlignment::CENTER => {}
|
||||
AnchorAlignment::END => right = true,
|
||||
}
|
||||
match geometry.anchor_point.y {
|
||||
AnchorAlignment::START => top = true,
|
||||
AnchorAlignment::CENTER => {}
|
||||
AnchorAlignment::END => bottom = true,
|
||||
}
|
||||
|
||||
window.set_anchor(gtk4_layer_shell::Edge::Left, left);
|
||||
window.set_anchor(gtk4_layer_shell::Edge::Right, right);
|
||||
window.set_anchor(gtk4_layer_shell::Edge::Top, top);
|
||||
window.set_anchor(gtk4_layer_shell::Edge::Bottom, bottom);
|
||||
|
||||
let xoffset = geometry.offset.x.pixels_relative_to(monitor.width());
|
||||
let yoffset = geometry.offset.y.pixels_relative_to(monitor.height());
|
||||
|
||||
if left {
|
||||
window.set_margin(gtk4_layer_shell::Edge::Left, xoffset);
|
||||
} else {
|
||||
window.set_margin(gtk4_layer_shell::Edge::Right, xoffset);
|
||||
}
|
||||
if bottom {
|
||||
window.set_margin(gtk4_layer_shell::Edge::Bottom, yoffset);
|
||||
} else {
|
||||
window.set_margin(gtk4_layer_shell::Edge::Top, yoffset);
|
||||
}
|
||||
// https://github.com/elkowar/eww/issues/296
|
||||
if window_init.backend_options.wayland.exclusive
|
||||
&& geometry.anchor_point.x != AnchorAlignment::CENTER
|
||||
&& geometry.anchor_point.y != AnchorAlignment::CENTER
|
||||
{
|
||||
log::warn!("When ':exclusive true' the anchor has to include 'center', otherwise exlcusive won't work")
|
||||
}
|
||||
}
|
||||
if window_init.backend_options.wayland.exclusive {
|
||||
window.auto_exclusive_zone_enable();
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the keyboard interactivity
|
||||
@@ -95,53 +157,6 @@ mod platform_wayland {
|
||||
WlWindowFocusable::OnDemand => window.set_keyboard_mode(KeyboardMode::OnDemand),
|
||||
}
|
||||
|
||||
if let Some(geometry) = window_init.geometry {
|
||||
// Positioning surface
|
||||
let mut top = false;
|
||||
let mut left = false;
|
||||
let mut right = false;
|
||||
let mut bottom = false;
|
||||
|
||||
match geometry.anchor_point.x {
|
||||
AnchorAlignment::START => left = true,
|
||||
AnchorAlignment::CENTER => {}
|
||||
AnchorAlignment::END => right = true,
|
||||
}
|
||||
match geometry.anchor_point.y {
|
||||
AnchorAlignment::START => top = true,
|
||||
AnchorAlignment::CENTER => {}
|
||||
AnchorAlignment::END => bottom = true,
|
||||
}
|
||||
|
||||
window.set_anchor(gtk_layer_shell::Edge::Left, left);
|
||||
window.set_anchor(gtk_layer_shell::Edge::Right, right);
|
||||
window.set_anchor(gtk_layer_shell::Edge::Top, top);
|
||||
window.set_anchor(gtk_layer_shell::Edge::Bottom, bottom);
|
||||
|
||||
let xoffset = geometry.offset.x.pixels_relative_to(monitor.width());
|
||||
let yoffset = geometry.offset.y.pixels_relative_to(monitor.height());
|
||||
|
||||
if left {
|
||||
window.set_layer_shell_margin(gtk_layer_shell::Edge::Left, xoffset);
|
||||
} else {
|
||||
window.set_layer_shell_margin(gtk_layer_shell::Edge::Right, xoffset);
|
||||
}
|
||||
if bottom {
|
||||
window.set_layer_shell_margin(gtk_layer_shell::Edge::Bottom, yoffset);
|
||||
} else {
|
||||
window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, yoffset);
|
||||
}
|
||||
// https://github.com/elkowar/eww/issues/296
|
||||
if window_init.backend_options.wayland.exclusive
|
||||
&& geometry.anchor_point.x != AnchorAlignment::CENTER
|
||||
&& geometry.anchor_point.y != AnchorAlignment::CENTER
|
||||
{
|
||||
log::warn!("When ':exclusive true' the anchor has to include 'center', otherwise exlcusive won't work")
|
||||
}
|
||||
}
|
||||
if window_init.backend_options.wayland.exclusive {
|
||||
window.auto_exclusive_zone_enable();
|
||||
}
|
||||
Some(window)
|
||||
}
|
||||
}
|
||||
@@ -152,11 +167,13 @@ mod platform_x11 {
|
||||
use crate::window::backend_window_options::Side;
|
||||
use crate::window::backend_window_options::X11WindowType;
|
||||
use crate::window::window_definition::WindowStacking;
|
||||
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
|
||||
use crate::window_initiator::WindowInitiator;
|
||||
use anyhow::{Context, Result};
|
||||
use gdk::Monitor;
|
||||
use gtk::gdk;
|
||||
use gtk::{self, prelude::*};
|
||||
use gtk4::gdk;
|
||||
use gtk4::Window;
|
||||
use gtk4::{self, prelude::*};
|
||||
use std::rc::Rc;
|
||||
use x11rb::protocol::xproto::ConnectionExt;
|
||||
|
||||
use x11rb::{
|
||||
@@ -176,23 +193,19 @@ mod platform_x11 {
|
||||
fn initialize_window(
|
||||
window_init: &WindowInitiator,
|
||||
_monitor: gdk::Rectangle,
|
||||
x: i32,
|
||||
y: i32,
|
||||
_x: i32,
|
||||
_y: i32,
|
||||
) -> Option<Window> {
|
||||
let window_type = if window_init.backend_options.x11.wm_ignore {
|
||||
gtk::WindowType::Popup
|
||||
} else {
|
||||
gtk::WindowType::Toplevel
|
||||
};
|
||||
let window = Window::new(window_type, x, y);
|
||||
let window = Window::new();
|
||||
if window_init.backend_options.x11.wm_ignore {
|
||||
// popup
|
||||
window.set_decorated(false);
|
||||
window.set_modal(true);
|
||||
}; // else: normal, toplevel
|
||||
// window.move_(x, y);
|
||||
|
||||
window.set_resizable(window_init.resizable);
|
||||
window.set_keep_above(window_init.stacking == WindowStacking::Foreground);
|
||||
window.set_keep_below(window_init.stacking == WindowStacking::Background);
|
||||
if window_init.backend_options.x11.sticky {
|
||||
window.stick();
|
||||
} else {
|
||||
window.unstick();
|
||||
}
|
||||
|
||||
Some(window)
|
||||
}
|
||||
}
|
||||
@@ -208,9 +221,9 @@ mod platform_x11 {
|
||||
}
|
||||
|
||||
struct X11BackendConnection {
|
||||
conn: RustConnection<DefaultStream>,
|
||||
conn: Rc<RustConnection<DefaultStream>>,
|
||||
root_window: u32,
|
||||
atoms: AtomCollection,
|
||||
atoms: Rc<AtomCollection>,
|
||||
}
|
||||
|
||||
impl X11BackendConnection {
|
||||
@@ -218,7 +231,11 @@ mod platform_x11 {
|
||||
let (conn, screen_num) = RustConnection::connect(None)?;
|
||||
let screen = conn.setup().roots[screen_num].clone();
|
||||
let atoms = AtomCollection::new(&conn)?.reply()?;
|
||||
Ok(X11BackendConnection { conn, root_window: screen.root, atoms })
|
||||
Ok(X11BackendConnection {
|
||||
conn: Rc::new(conn),
|
||||
root_window: screen.root,
|
||||
atoms: Rc::new(atoms),
|
||||
})
|
||||
}
|
||||
|
||||
fn set_xprops_for(
|
||||
@@ -229,11 +246,13 @@ mod platform_x11 {
|
||||
) -> Result<()> {
|
||||
let monitor_rect = monitor.geometry();
|
||||
let scale_factor = monitor.scale_factor() as u32;
|
||||
let gdk_window = window.window().context("Couldn't get gdk window from gtk window")?;
|
||||
let win_id = gdk_window
|
||||
.downcast_ref::<gdkx11::X11Window>()
|
||||
let gdk_surface =
|
||||
window.surface().context("Couldn't get gdk window from gtk window")?;
|
||||
let win_id = gdk_surface
|
||||
.downcast_ref::<gdk4_x11::X11Surface>()
|
||||
.context("Failed to get x11 window for gtk window")?
|
||||
.xid() as u32;
|
||||
|
||||
let strut_def = window_init.backend_options.x11.struts;
|
||||
let root_window_geometry = self.conn.get_geometry(self.root_window)?.reply()?;
|
||||
|
||||
@@ -306,8 +325,63 @@ mod platform_x11 {
|
||||
)?
|
||||
.check()?;
|
||||
|
||||
// apply the stickiness and fg/bg thingy
|
||||
let sticky_clone = window_init.backend_options.x11.sticky.clone();
|
||||
let stacking_clone = window_init.stacking.clone();
|
||||
let root_window = self.root_window;
|
||||
let conn = Rc::clone(&self.conn);
|
||||
let atoms = Rc::clone(&self.atoms);
|
||||
|
||||
window.connect_show(move |_| {
|
||||
if let Err(err) = Self::set_window_states(
|
||||
&*conn,
|
||||
win_id,
|
||||
&*atoms,
|
||||
sticky_clone,
|
||||
stacking_clone,
|
||||
root_window,
|
||||
) {
|
||||
log::error!("Failed to set window state: {}", err);
|
||||
}
|
||||
});
|
||||
|
||||
self.conn.flush().context("Failed to send requests to X server")
|
||||
}
|
||||
|
||||
fn set_window_states(
|
||||
conn: &impl Connection,
|
||||
win: u32,
|
||||
atoms: &AtomCollection,
|
||||
sticky: bool,
|
||||
stacking: WindowStacking,
|
||||
root_window: u32,
|
||||
) -> Result<()> {
|
||||
let mut states = Vec::new();
|
||||
if sticky {
|
||||
states.push(atoms._NET_WM_STATE_STICKY);
|
||||
}
|
||||
match stacking {
|
||||
WindowStacking::Foreground => states.push(atoms._NET_WM_STATE_ABOVE),
|
||||
WindowStacking::Background => states.push(atoms._NET_WM_STATE_BELOW),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
for state in states {
|
||||
let event = ClientMessageEvent {
|
||||
response_type: CLIENT_MESSAGE_EVENT,
|
||||
format: 32,
|
||||
sequence: 0,
|
||||
window: win,
|
||||
type_: atoms._NET_WM_STATE,
|
||||
data: ClientMessageData::from([1, state, 0, 0, 0]),
|
||||
};
|
||||
|
||||
conn.send_event(true, root_window, EventMask::PROPERTY_CHANGE, event)?;
|
||||
}
|
||||
|
||||
conn.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
x11rb::atom_manager! {
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
#![allow(rustdoc::private_intra_doc_links)]
|
||||
|
||||
// getting gtk stuff
|
||||
extern crate gtk;
|
||||
extern crate gtk4;
|
||||
#[cfg(feature = "wayland")]
|
||||
extern crate gtk_layer_shell as gtk_layer_shell;
|
||||
extern crate gtk4_layer_shell as gtk4_layer_shell;
|
||||
|
||||
// imporing dependencies
|
||||
use anyhow::{Context, Result};
|
||||
@@ -28,7 +28,7 @@ use clap::CommandFactory as _;
|
||||
use daemon_response::{DaemonResponse, DaemonResponseReceiver};
|
||||
use display_backend::DisplayBackend;
|
||||
use opts::ActionWithServer;
|
||||
use paths::EwwPaths;
|
||||
use paths::EwwiiPaths;
|
||||
use std::{os::unix::net, path::Path, time::Duration};
|
||||
|
||||
use crate::server::ForkResult;
|
||||
@@ -48,6 +48,7 @@ mod gen_diagnostic_macro;
|
||||
mod ipc_server;
|
||||
mod opts;
|
||||
mod paths;
|
||||
mod plugin;
|
||||
mod server;
|
||||
mod util;
|
||||
mod widgets;
|
||||
@@ -56,9 +57,8 @@ mod window_arguments;
|
||||
mod window_initiator;
|
||||
|
||||
fn main() {
|
||||
// gets the eww binary
|
||||
let eww_binary_name = std::env::args().next().unwrap();
|
||||
let opts: opts::Opt = opts::Opt::from_env(); // opts of clap (from ./opts.rs)
|
||||
let ewwii_binary_name = std::env::args().next().unwrap();
|
||||
let opts: opts::Opt = opts::Opt::from_env();
|
||||
|
||||
let trace_enabled = std::env::var("EWWII_TRACE").is_ok();
|
||||
|
||||
@@ -101,14 +101,14 @@ fn main() {
|
||||
opts.force_wayland,
|
||||
detected_wayland
|
||||
);
|
||||
run::<display_backend::WaylandBackend>(opts, eww_binary_name)
|
||||
run::<display_backend::WaylandBackend>(opts, ewwii_binary_name)
|
||||
} else {
|
||||
log::debug!(
|
||||
"Running on X11. force_wayland={}, detected_wayland={}",
|
||||
opts.force_wayland,
|
||||
detected_wayland
|
||||
);
|
||||
run::<display_backend::X11Backend>(opts, eww_binary_name)
|
||||
run::<display_backend::X11Backend>(opts, ewwii_binary_name)
|
||||
};
|
||||
|
||||
#[cfg(all(not(feature = "wayland"), feature = "x11"))]
|
||||
@@ -116,14 +116,14 @@ fn main() {
|
||||
if use_wayland {
|
||||
log::warn!("Ewwii compiled without wayland support. Falling back to X11, eventhough wayland was requested.");
|
||||
}
|
||||
run::<display_backend::X11Backend>(opts, eww_binary_name)
|
||||
run::<display_backend::X11Backend>(opts, ewwii_binary_name)
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "wayland", not(feature = "x11")))]
|
||||
let result = run::<display_backend::WaylandBackend>(opts, eww_binary_name);
|
||||
let result = run::<display_backend::WaylandBackend>(opts, ewwii_binary_name);
|
||||
|
||||
#[cfg(not(any(feature = "wayland", feature = "x11")))]
|
||||
let result = run::<display_backend::NoBackend>(opts, eww_binary_name);
|
||||
let result = run::<display_backend::NoBackend>(opts, ewwii_binary_name);
|
||||
|
||||
if let Err(err) = result {
|
||||
error_handling_ctx::print_error(err);
|
||||
@@ -139,16 +139,16 @@ fn detect_wayland() -> bool {
|
||||
|| (!wayland_display.is_empty() && !session_type.contains("x11"))
|
||||
}
|
||||
|
||||
fn run<B: DisplayBackend>(opts: opts::Opt, eww_binary_name: String) -> Result<()> {
|
||||
fn run<B: DisplayBackend>(opts: opts::Opt, ewwii_binary_name: String) -> Result<()> {
|
||||
let paths = opts
|
||||
.config_path
|
||||
.map(EwwPaths::from_config_dir)
|
||||
.unwrap_or_else(EwwPaths::default)
|
||||
.map(EwwiiPaths::from_config_dir)
|
||||
.unwrap_or_else(EwwiiPaths::default)
|
||||
.context("Failed to initialize ewwii paths")?;
|
||||
|
||||
let should_restart = match &opts.action {
|
||||
opts::Action::ShellCompletions { .. } => unreachable!(),
|
||||
opts::Action::Daemon => opts.restart,
|
||||
opts::Action::Daemon { .. } => opts.restart,
|
||||
opts::Action::WithServer(action) => opts.restart && action.can_start_daemon(),
|
||||
opts::Action::ClientOnly(_) => false,
|
||||
};
|
||||
@@ -167,24 +167,27 @@ fn run<B: DisplayBackend>(opts: opts::Opt, eww_binary_name: String) -> Result<()
|
||||
false
|
||||
}
|
||||
|
||||
// make sure that there isn't already a Eww daemon running.
|
||||
opts::Action::Daemon if check_server_running(paths.get_ipc_socket_file()) => {
|
||||
// make sure that there isn't already a Ewwii daemon running.
|
||||
opts::Action::Daemon { .. } if check_server_running(paths.get_ipc_socket_file()) => {
|
||||
eprintln!("Ewwii server already running.");
|
||||
true
|
||||
}
|
||||
// initializing the eww server i see..
|
||||
opts::Action::Daemon => {
|
||||
opts::Action::Daemon { with_plugin } => {
|
||||
log::info!("Initializing Ewwii server. ({})", paths.get_ipc_socket_file().display());
|
||||
let _ = std::fs::remove_file(paths.get_ipc_socket_file());
|
||||
|
||||
if !opts.show_logs {
|
||||
println!(
|
||||
"Run `{} logs` to see any errors while editing your configuration.",
|
||||
eww_binary_name
|
||||
ewwii_binary_name
|
||||
);
|
||||
}
|
||||
let fork_result =
|
||||
server::initialize_server::<B>(paths.clone(), None, !opts.no_daemonize)?;
|
||||
let fork_result = server::initialize_server::<B>(
|
||||
paths.clone(),
|
||||
None,
|
||||
!opts.no_daemonize,
|
||||
with_plugin,
|
||||
)?;
|
||||
opts.no_daemonize || fork_result == ForkResult::Parent
|
||||
}
|
||||
|
||||
@@ -217,14 +220,14 @@ fn run<B: DisplayBackend>(opts: opts::Opt, eww_binary_name: String) -> Result<()
|
||||
if !opts.show_logs {
|
||||
println!(
|
||||
"Run `{} logs` to see any errors while editing your configuration.",
|
||||
eww_binary_name
|
||||
ewwii_binary_name
|
||||
);
|
||||
}
|
||||
|
||||
let (command, response_recv) = action.into_daemon_command();
|
||||
// start the daemon and give it the command
|
||||
let fork_result =
|
||||
server::initialize_server::<B>(paths.clone(), Some(command), true)?;
|
||||
server::initialize_server::<B>(paths.clone(), Some(command), true, None)?;
|
||||
let is_parent = fork_result == ForkResult::Parent;
|
||||
if let (Some(recv), true) = (response_recv, is_parent) {
|
||||
listen_for_daemon_response(recv);
|
||||
@@ -259,7 +262,7 @@ fn listen_for_daemon_response(mut recv: DaemonResponseReceiver) {
|
||||
|
||||
/// attempt to send a command to the daemon and send it the given action repeatedly.
|
||||
fn handle_server_command(
|
||||
paths: &EwwPaths,
|
||||
paths: &EwwiiPaths,
|
||||
action: &ActionWithServer,
|
||||
connect_attempts: usize,
|
||||
) -> Result<Option<DaemonResponse>> {
|
||||
@@ -295,7 +298,7 @@ fn attempt_connect(socket_path: impl AsRef<Path>, attempts: usize) -> Option<net
|
||||
None
|
||||
}
|
||||
|
||||
/// Check if a eww server is currently running by trying to send a ping message to it.
|
||||
/// Check if a ewwii server is currently running by trying to send a ping message to it.
|
||||
fn check_server_running(socket_path: impl AsRef<Path>) -> bool {
|
||||
let response = net::UnixStream::connect(socket_path).ok().and_then(|mut stream| {
|
||||
client::do_server_call(&mut stream, &opts::ActionWithServer::Ping).ok()
|
||||
|
||||
@@ -67,7 +67,10 @@ pub enum Action {
|
||||
|
||||
/// Start the Ewwii daemon.
|
||||
#[command(name = "daemon", alias = "d")]
|
||||
Daemon,
|
||||
Daemon {
|
||||
#[arg(long)]
|
||||
with_plugin: Option<String>,
|
||||
},
|
||||
|
||||
#[command(flatten)]
|
||||
ClientOnly(ActionClientOnly),
|
||||
@@ -164,13 +167,9 @@ pub enum ActionWithServer {
|
||||
#[command(name = "close-all", alias = "ca")]
|
||||
CloseAll,
|
||||
|
||||
// /// Prints the variables used in all currently open window
|
||||
// #[command(name = "state")]
|
||||
// ShowState {
|
||||
// /// Shows all variables, including not currently used ones
|
||||
// #[arg(short, long)]
|
||||
// all: bool,
|
||||
// },
|
||||
/// Prints all the variables in the registery
|
||||
#[command(name = "state")]
|
||||
ShowState,
|
||||
/// List the names of active windows
|
||||
#[command(name = "list-windows")]
|
||||
ListWindows,
|
||||
@@ -188,24 +187,111 @@ pub enum ActionWithServer {
|
||||
// /// Print out the scope graph structure in graphviz dot format.
|
||||
// #[command(name = "graph")]
|
||||
// ShowGraph,
|
||||
/// Control widgets through CLI.
|
||||
#[command(name = "widget-control", alias = "wc")]
|
||||
WidgetControl {
|
||||
#[command(subcommand)]
|
||||
action: WidgetControlAction,
|
||||
},
|
||||
|
||||
/// Update the widgets of a particular window. Poll/Listen variables will be cleared
|
||||
#[command(name = "update", alias = "u")]
|
||||
TriggerUpdateUI {
|
||||
/// Inject variables while updating the UI
|
||||
///
|
||||
/// Format: --inject-vars foo="val1" baz="val2"
|
||||
/// Format: --inject foo="val1" baz="val2"
|
||||
/// Only variables used by the widget tree will affect the UI.
|
||||
#[arg(long, value_parser = parse_inject_var_map)]
|
||||
#[arg(long = "inject", short = 'i', value_parser = parse_inject_var_map)]
|
||||
inject_vars: Option<HashMap<String, String>>,
|
||||
|
||||
/// Preserve the new updates.
|
||||
#[arg(long = "preserve", short = 'p')]
|
||||
should_preserve_state: bool,
|
||||
|
||||
/// Tie the variable lifetime to a window lifetime.
|
||||
#[arg(long = "lifetime", short = 'l')]
|
||||
lifetime: Option<String>,
|
||||
},
|
||||
|
||||
/// Call rhai functions. (NOTE: All poll/listen will default to their initial value)
|
||||
#[command(name = "call-fns")]
|
||||
CallRhaiFns {
|
||||
// Rhai functions to call. Format: --fn-calls "fn_name1(args)" "fn_name2(args)"
|
||||
/// Rhai functions to call. Format: call-fns "fn_name1(args)" "fn_name2(args)"
|
||||
#[arg(required = true)]
|
||||
calls: Vec<String>,
|
||||
},
|
||||
|
||||
/// Override the default runtime engine settings
|
||||
#[command(name = "engine-override")]
|
||||
EngineOverride {
|
||||
/// Configuration in JSON format
|
||||
config_json: String,
|
||||
|
||||
/// Weather to print the current engine settings
|
||||
#[arg(long = "sprint", short = 'p')]
|
||||
print: bool,
|
||||
},
|
||||
|
||||
/// Set a plugin (.so) to the ewwii binary
|
||||
#[command(name = "set-plugin")]
|
||||
SetPlugin {
|
||||
/// The .so file to load
|
||||
#[arg(value_parser = absolute_file_path_parser)]
|
||||
file_path: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// Subcommands for widget control
|
||||
#[derive(Subcommand, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum WidgetControlAction {
|
||||
/// Remove widget by name
|
||||
Remove {
|
||||
/// Names of the widgets to remove
|
||||
names: Vec<String>,
|
||||
},
|
||||
|
||||
/// Create widgets
|
||||
Create {
|
||||
/// Rhai code to create widgets from
|
||||
rhai_codes: Vec<String>,
|
||||
|
||||
/// Name of the widget to add these widgets as a child to
|
||||
#[arg(long = "parent", short = 'p')]
|
||||
parent_name: String,
|
||||
},
|
||||
|
||||
/// Update properties of a widget by name
|
||||
PropertyUpdate {
|
||||
/// Properties and its value
|
||||
///
|
||||
/// Format: value="val1" widget_name="val2"
|
||||
#[arg(value_parser = parse_inject_var_map)]
|
||||
property_and_value: HashMap<String, String>,
|
||||
|
||||
/// Name of the widget to update the property of
|
||||
#[arg(long = "widget", short = 'w')]
|
||||
widget_name: String,
|
||||
},
|
||||
|
||||
/// Add a class to a widget with given name
|
||||
AddClass {
|
||||
/// The class to add to the widget
|
||||
class: String,
|
||||
|
||||
/// Name of the widget to add class to
|
||||
#[arg(long = "widget", short = 'w')]
|
||||
widget_name: String,
|
||||
},
|
||||
|
||||
/// Remove a class to a widget with given name
|
||||
RemoveClass {
|
||||
/// The class to remove from the widget
|
||||
class: String,
|
||||
|
||||
/// Name of the widget to remove class from
|
||||
#[arg(long = "widget", short = 'w')]
|
||||
widget_name: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Opt {
|
||||
@@ -267,8 +353,19 @@ impl ActionWithServer {
|
||||
self,
|
||||
) -> (app::DaemonCommand, Option<daemon_response::DaemonResponseReceiver>) {
|
||||
let command = match self {
|
||||
ActionWithServer::TriggerUpdateUI { inject_vars } => {
|
||||
app::DaemonCommand::TriggerUpdateUI(inject_vars)
|
||||
ActionWithServer::WidgetControl { action } => {
|
||||
return with_response_channel(|sender| app::DaemonCommand::WidgetControl {
|
||||
action,
|
||||
sender,
|
||||
})
|
||||
}
|
||||
ActionWithServer::TriggerUpdateUI { inject_vars, should_preserve_state, lifetime } => {
|
||||
return with_response_channel(|sender| app::DaemonCommand::TriggerUpdateUI {
|
||||
inject_vars,
|
||||
should_preserve_state,
|
||||
lifetime,
|
||||
sender,
|
||||
})
|
||||
}
|
||||
ActionWithServer::CallRhaiFns { calls } => {
|
||||
return with_response_channel(|sender| app::DaemonCommand::CallRhaiFns {
|
||||
@@ -276,7 +373,6 @@ impl ActionWithServer {
|
||||
sender,
|
||||
})
|
||||
}
|
||||
|
||||
ActionWithServer::OpenInspector => app::DaemonCommand::OpenInspector,
|
||||
|
||||
ActionWithServer::KillServer => app::DaemonCommand::KillServer,
|
||||
@@ -322,6 +418,9 @@ impl ActionWithServer {
|
||||
ActionWithServer::Reload => {
|
||||
return with_response_channel(app::DaemonCommand::ReloadConfigAndCss)
|
||||
}
|
||||
ActionWithServer::ShowState => {
|
||||
return with_response_channel(app::DaemonCommand::ShowState)
|
||||
}
|
||||
ActionWithServer::ListWindows => {
|
||||
return with_response_channel(app::DaemonCommand::ListWindows)
|
||||
}
|
||||
@@ -331,6 +430,19 @@ impl ActionWithServer {
|
||||
ActionWithServer::ShowDebug => {
|
||||
return with_response_channel(app::DaemonCommand::PrintDebug)
|
||||
}
|
||||
ActionWithServer::EngineOverride { config_json, print } => {
|
||||
return with_response_channel(|sender| app::DaemonCommand::EngineOverride {
|
||||
config: config_json,
|
||||
print,
|
||||
sender,
|
||||
})
|
||||
}
|
||||
ActionWithServer::SetPlugin { file_path } => {
|
||||
return with_response_channel(|sender| app::DaemonCommand::SetPlugin {
|
||||
file_path,
|
||||
sender,
|
||||
})
|
||||
}
|
||||
};
|
||||
(command, None)
|
||||
}
|
||||
@@ -407,3 +519,10 @@ fn parse_inject_var_map(s: &str) -> Result<HashMap<String, String>, String> {
|
||||
}
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
fn absolute_file_path_parser(s: &str) -> Result<String, String> {
|
||||
let p = std::path::Path::new(s);
|
||||
std::fs::canonicalize(p)
|
||||
.map_err(|e| format!("Failed to canonicalize '{}': {}", s, e))
|
||||
.map(|abs_path| abs_path.to_string_lossy().into_owned())
|
||||
}
|
||||
|
||||
@@ -6,17 +6,16 @@ use std::{
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
/// Stores references to all the paths relevant to eww, and abstracts access to these files and directories
|
||||
/// Stores references to all the paths relevant to ewwii, and abstracts access to these files and directories
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EwwPaths {
|
||||
pub struct EwwiiPaths {
|
||||
pub log_file: PathBuf,
|
||||
pub log_dir: PathBuf,
|
||||
pub ipc_socket_file: PathBuf,
|
||||
pub config_dir: PathBuf,
|
||||
}
|
||||
|
||||
// all the eww paths i suppose from the name?
|
||||
impl EwwPaths {
|
||||
impl EwwiiPaths {
|
||||
pub fn from_config_dir<P: AsRef<Path>>(config_dir: P) -> Result<Self> {
|
||||
let config_dir = config_dir.as_ref();
|
||||
if config_dir.is_file() {
|
||||
@@ -55,9 +54,9 @@ impl EwwPaths {
|
||||
std::fs::create_dir_all(&log_dir)?;
|
||||
}
|
||||
|
||||
Ok(EwwPaths {
|
||||
Ok(Self {
|
||||
config_dir,
|
||||
log_file: log_dir.join(format!("eww_{}.log", daemon_id)),
|
||||
log_file: log_dir.join(format!("ewwii_{}.log", daemon_id)),
|
||||
log_dir,
|
||||
ipc_socket_file,
|
||||
})
|
||||
@@ -94,7 +93,7 @@ impl EwwPaths {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for EwwPaths {
|
||||
impl std::fmt::Display for EwwiiPaths {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
|
||||
86
crates/ewwii/src/plugin.rs
Normal file
86
crates/ewwii/src/plugin.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use ewwii_plugin_api::{rhai_backend, widget_backend, EwwiiAPI};
|
||||
use rhai::{Array, Dynamic, Engine, EvalAltResult};
|
||||
use std::sync::mpsc::{channel as mpsc_channel, Receiver, Sender};
|
||||
|
||||
pub(crate) struct EwwiiImpl {
|
||||
pub(crate) requestor: Sender<PluginRequest>,
|
||||
}
|
||||
|
||||
impl EwwiiAPI for EwwiiImpl {
|
||||
// General
|
||||
// "PCL = Plugin Controlled Log"
|
||||
fn print(&self, msg: &str) {
|
||||
println!("[PCL] {}", msg);
|
||||
}
|
||||
|
||||
fn log(&self, msg: &str) {
|
||||
log::info!("[PCL] {}", msg);
|
||||
}
|
||||
|
||||
fn warn(&self, msg: &str) {
|
||||
log::warn!("[PCL] {}", msg);
|
||||
}
|
||||
|
||||
fn error(&self, msg: &str) {
|
||||
log::error!("[PCL] {}", msg);
|
||||
}
|
||||
|
||||
// Rhai Manipulation Stuff
|
||||
fn rhai_engine_action(&self, f: Box<dyn FnOnce(&mut Engine) + Send>) -> Result<(), String> {
|
||||
self.requestor
|
||||
.send(PluginRequest::RhaiEngineAct(f))
|
||||
.map_err(|_| "Failed to send request to host".to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_function(
|
||||
&self,
|
||||
name: String,
|
||||
namespace: rhai_backend::RhaiFnNamespace,
|
||||
f: Box<dyn Fn(Array) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync>,
|
||||
) -> Result<(), String> {
|
||||
let func_info = (name, namespace, f);
|
||||
|
||||
self.requestor
|
||||
.send(PluginRequest::RegisterFunc(func_info))
|
||||
.map_err(|_| "Failed to send request to host".to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Widget Rendering & Logic
|
||||
fn list_widget_ids(&self) -> Result<Vec<u64>, String> {
|
||||
let (tx, rx): (Sender<Vec<u64>>, Receiver<Vec<u64>>) = mpsc_channel();
|
||||
|
||||
self.requestor
|
||||
.send(PluginRequest::ListWidgetIds(tx))
|
||||
.map_err(|_| "Failed to send request to host".to_string())?;
|
||||
|
||||
match rx.recv() {
|
||||
Ok(r) => Ok(r),
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn widget_reg_action(
|
||||
&self,
|
||||
f: Box<dyn FnOnce(&mut widget_backend::WidgetRegistryRepr) + Send>,
|
||||
) -> Result<(), String> {
|
||||
self.requestor
|
||||
.send(PluginRequest::WidgetRegistryAct(f))
|
||||
.map_err(|_| "Failed to send request to host".to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum PluginRequest {
|
||||
RhaiEngineAct(Box<dyn FnOnce(&mut Engine) + Send>),
|
||||
RegisterFunc(
|
||||
(
|
||||
String,
|
||||
rhai_backend::RhaiFnNamespace,
|
||||
Box<dyn Fn(Array) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync>,
|
||||
),
|
||||
),
|
||||
ListWidgetIds(Sender<Vec<u64>>),
|
||||
WidgetRegistryAct(Box<dyn FnOnce(&mut widget_backend::WidgetRegistryRepr) + Send>),
|
||||
}
|
||||
@@ -1,27 +1,28 @@
|
||||
use crate::{
|
||||
app::{self, App, DaemonCommand},
|
||||
app::{self, App, DaemonCommand, EngineConfValues},
|
||||
config, daemon_response,
|
||||
display_backend::DisplayBackend,
|
||||
error_handling_ctx, ipc_server, EwwPaths,
|
||||
error_handling_ctx, ipc_server, EwwiiPaths,
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use gtk4::prelude::{DisplayExt, ListModelExt};
|
||||
use std::{
|
||||
// cell::RefCell,
|
||||
cell::RefCell,
|
||||
collections::{HashMap, HashSet},
|
||||
io::Write,
|
||||
marker::PhantomData,
|
||||
os::unix::io::AsRawFd,
|
||||
path::Path,
|
||||
// rc::Rc,
|
||||
sync::{atomic::Ordering, Arc},
|
||||
rc::Rc,
|
||||
sync::{atomic::Ordering, Arc, RwLock},
|
||||
};
|
||||
use tokio::sync::mpsc::*;
|
||||
|
||||
pub fn initialize_server<B: DisplayBackend>(
|
||||
paths: EwwPaths,
|
||||
paths: EwwiiPaths,
|
||||
action: Option<DaemonCommand>,
|
||||
should_daemonize: bool,
|
||||
ewwii_plugin_path: Option<String>,
|
||||
) -> Result<ForkResult> {
|
||||
let (ui_send, mut ui_recv) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
@@ -31,18 +32,11 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||
|
||||
log::info!("Loading paths: {}", &paths);
|
||||
|
||||
let read_config = config::read_from_ewwii_paths(&paths);
|
||||
let pl_handler_store: rhai_impl::updates::ReactiveVarStore =
|
||||
Arc::new(RwLock::new(HashMap::new()));
|
||||
|
||||
let ewwii_config = match read_config {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
error_handling_ctx::print_error(err);
|
||||
config::EwwiiConfig::default()
|
||||
|
||||
// TODO: Maybe do something so that we can exit if user wants.
|
||||
// std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let config_parser =
|
||||
Rc::new(RefCell::new(rhai_impl::parser::ParseConfig::new(Some(pl_handler_store.clone()))));
|
||||
|
||||
cleanup_log_dir(paths.get_log_dir())?;
|
||||
|
||||
@@ -76,28 +70,57 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||
if B::IS_WAYLAND {
|
||||
std::env::set_var("GDK_BACKEND", "wayland")
|
||||
}
|
||||
gtk::init()?;
|
||||
|
||||
gtk4::init()?;
|
||||
|
||||
let main_loop = gtk4::glib::MainLoop::new(None, false);
|
||||
|
||||
let mut app: App<B> = app::App {
|
||||
ewwii_config,
|
||||
ewwii_config: config::EwwiiConfig::default(),
|
||||
open_windows: HashMap::new(),
|
||||
failed_windows: HashSet::new(),
|
||||
instance_id_to_args: HashMap::new(),
|
||||
css_provider: gtk::CssProvider::new(),
|
||||
css_provider: gtk4::CssProvider::new(),
|
||||
reloading: false,
|
||||
app_evt_send: ui_send.clone(),
|
||||
window_close_timer_abort_senders: HashMap::new(),
|
||||
widget_reg_store: std::rc::Rc::new(std::sync::Mutex::new(None)),
|
||||
pl_handler_store: None,
|
||||
pl_handler_store,
|
||||
clear_pl_onclose: HashMap::new(),
|
||||
rt_engine_config: EngineConfValues::default(),
|
||||
config_parser,
|
||||
paths,
|
||||
gtk_main_loop: main_loop.clone(),
|
||||
phantom: PhantomData,
|
||||
};
|
||||
|
||||
if let Some(screen) = gtk::gdk::Screen::default() {
|
||||
gtk::StyleContext::add_provider_for_screen(
|
||||
&screen,
|
||||
&app.css_provider,
|
||||
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||
);
|
||||
// start up plugins
|
||||
if let Some(ewwii_plugin) = ewwii_plugin_path {
|
||||
if let Err(e) = app.set_ewwii_plugin(ewwii_plugin) {
|
||||
error_handling_ctx::print_error(e);
|
||||
}
|
||||
}
|
||||
|
||||
let mut config_parser_mut = app.config_parser.borrow_mut();
|
||||
let read_config = config::read_from_ewwii_paths(&app.paths, &mut *config_parser_mut);
|
||||
|
||||
// free the temporary parser borrow
|
||||
drop(config_parser_mut);
|
||||
|
||||
match read_config {
|
||||
Ok(new_config) => {
|
||||
app.ewwii_config = new_config;
|
||||
}
|
||||
Err(err) => {
|
||||
error_handling_ctx::print_error(err);
|
||||
|
||||
// TODO: Maybe do something so that we can exit if user wants.
|
||||
// std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(display) = gtk4::gdk::Display::default() {
|
||||
gtk4::style_context_add_provider_for_display(&display, &app.css_provider, 900);
|
||||
}
|
||||
|
||||
if let Ok((file_id, css)) = config::scss::parse_scss_from_config(app.paths.get_config_dir()) {
|
||||
@@ -111,7 +134,7 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||
// initialize all the handlers and tasks running asyncronously
|
||||
let tokio_handle = init_async_part(app.paths.clone(), ui_send);
|
||||
|
||||
gtk::glib::MainContext::default().spawn_local(async move {
|
||||
gtk4::glib::MainContext::default().spawn_local(async move {
|
||||
// if an action was given to the daemon initially, execute it first.
|
||||
if let Some(action) = action {
|
||||
app.handle_command(action).await;
|
||||
@@ -133,20 +156,26 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||
// allow the GTK main thread to do tokio things
|
||||
let _g = tokio_handle.enter();
|
||||
|
||||
gtk::main();
|
||||
main_loop.run();
|
||||
log::info!("main application thread finished");
|
||||
|
||||
Ok(ForkResult::Child)
|
||||
}
|
||||
|
||||
fn connect_monitor_added(ui_send: UnboundedSender<DaemonCommand>) {
|
||||
let display = gtk::gdk::Display::default().expect("could not get default display");
|
||||
display.connect_monitor_added({
|
||||
move |_display: >k::gdk::Display, _monitor: >k::gdk::Monitor| {
|
||||
log::info!("New monitor connected, reloading configuration");
|
||||
let _ = reload_config_and_css(&ui_send);
|
||||
}
|
||||
});
|
||||
if let Some(display) = gtk4::gdk::Display::default() {
|
||||
let monitors = display.monitors();
|
||||
|
||||
monitors.connect_items_changed(gtk4::glib::clone!(
|
||||
#[strong]
|
||||
ui_send,
|
||||
move |_, _, _, _| {
|
||||
let _ = reload_config_and_css(&ui_send);
|
||||
}
|
||||
));
|
||||
} else {
|
||||
log::warn!("Cannot access GDK Display on this session (likely Wayland)");
|
||||
}
|
||||
}
|
||||
|
||||
fn reload_config_and_css(ui_send: &UnboundedSender<DaemonCommand>) -> Result<()> {
|
||||
@@ -165,7 +194,7 @@ fn reload_config_and_css(ui_send: &UnboundedSender<DaemonCommand>) -> Result<()>
|
||||
}
|
||||
|
||||
fn init_async_part(
|
||||
paths: EwwPaths,
|
||||
paths: EwwiiPaths,
|
||||
ui_send: UnboundedSender<app::DaemonCommand>,
|
||||
) -> tokio::runtime::Handle {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
@@ -316,7 +345,7 @@ fn do_detach(log_file_path: impl AsRef<Path>) -> Result<ForkResult> {
|
||||
/// Ensure the log directory never grows larger than 100MB by deleting files older than 7 days,
|
||||
/// and truncating all other logfiles to 100MB.
|
||||
fn cleanup_log_dir(log_dir: impl AsRef<Path>) -> Result<()> {
|
||||
// Find all files named "eww_*.log" in the log directory
|
||||
// Find all files named "ewwii_*.log" in the log directory
|
||||
let log_files = std::fs::read_dir(&log_dir)?
|
||||
.filter_map(|entry| {
|
||||
let entry = entry.ok()?;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use anyhow::Result;
|
||||
use gtk::gdk::prelude::Cast;
|
||||
use gtk4::gdk::prelude::Cast;
|
||||
|
||||
use crate::{config::WindowDefinition, widgets::widget_definitions::*};
|
||||
use crate::config::WindowDefinition;
|
||||
use crate::widgets::widget_definitions::*;
|
||||
|
||||
use rhai_impl::ast::WidgetNode;
|
||||
|
||||
@@ -17,7 +18,7 @@ pub enum WidgetInput<'a> {
|
||||
pub fn build_gtk_widget<'a>(
|
||||
input: &'a WidgetInput<'a>,
|
||||
widget_reg: &mut WidgetRegistry,
|
||||
) -> Result<gtk::Widget> {
|
||||
) -> Result<gtk4::Widget> {
|
||||
let node: &'a WidgetNode = match input {
|
||||
WidgetInput::Node(n) => n,
|
||||
WidgetInput::BorrowedNode(n) => n,
|
||||
@@ -30,7 +31,7 @@ pub fn build_gtk_widget<'a>(
|
||||
fn build_gtk_widget_from_node(
|
||||
root_node: &WidgetNode,
|
||||
widget_reg: &mut WidgetRegistry,
|
||||
) -> Result<gtk::Widget> {
|
||||
) -> Result<gtk4::Widget> {
|
||||
/*
|
||||
When a a new widget is added to the build process,
|
||||
make sure to update get_id_to_props_map() found in
|
||||
@@ -40,23 +41,30 @@ fn build_gtk_widget_from_node(
|
||||
|
||||
let gtk_widget = match root_node {
|
||||
WidgetNode::Box { props, children } => build_gtk_box(props, children, widget_reg)?.upcast(),
|
||||
WidgetNode::CenterBox { props, children } => {
|
||||
build_center_box(props, children, widget_reg)?.upcast()
|
||||
WidgetNode::FlowBox { props, children } => {
|
||||
build_gtk_flowbox(props, children, widget_reg)?.upcast()
|
||||
}
|
||||
WidgetNode::EventBox { props, children } => {
|
||||
build_gtk_event_box(props, children, widget_reg)?.upcast()
|
||||
build_event_box(props, children, widget_reg)?.upcast()
|
||||
}
|
||||
WidgetNode::ToolTip { props, children } => {
|
||||
build_tooltip(props, children, widget_reg)?.upcast()
|
||||
}
|
||||
WidgetNode::LocalBind { props, children } => {
|
||||
build_localbind_util(props, children, widget_reg)?.upcast()
|
||||
}
|
||||
WidgetNode::WidgetAction { props, children } => {
|
||||
build_widgetaction_util(props, children, widget_reg)?.upcast()
|
||||
}
|
||||
WidgetNode::CircularProgress { props } => {
|
||||
build_circular_progress_bar(props, widget_reg)?.upcast()
|
||||
}
|
||||
WidgetNode::Graph { props } => build_graph(props, widget_reg)?.upcast(),
|
||||
WidgetNode::Transform { props } => build_transform(props, widget_reg)?.upcast(),
|
||||
WidgetNode::Slider { props } => build_gtk_scale(props, widget_reg)?.upcast(),
|
||||
WidgetNode::GtkUI { props } => build_gtk_ui_file(props)?.upcast(),
|
||||
// WidgetNode::Graph { props } => build_graph(props, widget_reg)?.upcast(),
|
||||
// WidgetNode::Transform { props } => build_transform(props, widget_reg)?.upcast(),
|
||||
WidgetNode::Scale { props } => build_gtk_scale(props, widget_reg)?.upcast(),
|
||||
WidgetNode::Progress { props } => build_gtk_progress(props, widget_reg)?.upcast(),
|
||||
WidgetNode::Image { props } => build_gtk_image(props, widget_reg)?.upcast(),
|
||||
WidgetNode::Image { props } => build_image(props, widget_reg)?.upcast(),
|
||||
WidgetNode::Button { props } => build_gtk_button(props, widget_reg)?.upcast(),
|
||||
WidgetNode::Label { props } => build_gtk_label(props, widget_reg)?.upcast(),
|
||||
// WIDGET_NAME_LITERAL => build_gtk_literal(node)?.upcast(),
|
||||
|
||||
@@ -1,206 +1,124 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use gtk::glib::{self, object_subclass, prelude::*, wrapper, Properties};
|
||||
use gtk::{cairo, gdk, prelude::*, subclass::prelude::*};
|
||||
use std::cell::RefCell;
|
||||
use glib::Object;
|
||||
use gtk4::glib;
|
||||
use gtk4::prelude::*;
|
||||
use gtk4::subclass::prelude::*;
|
||||
use gtk4::{cairo, gdk, graphene};
|
||||
use std::cell::Cell;
|
||||
|
||||
use crate::error_handling_ctx;
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
wrapper! {
|
||||
pub struct CircProg(ObjectSubclass<CircProgPriv>)
|
||||
@extends gtk::Bin, gtk::Container, gtk::Widget;
|
||||
}
|
||||
|
||||
#[derive(Properties)]
|
||||
#[properties(wrapper_type = CircProg)]
|
||||
pub struct CircProgPriv {
|
||||
#[property(
|
||||
get,
|
||||
set,
|
||||
nick = "Starting at",
|
||||
blurb = "Starting at",
|
||||
minimum = 0f64,
|
||||
maximum = 100f64,
|
||||
default = 0f64
|
||||
)]
|
||||
start_at: RefCell<f64>,
|
||||
|
||||
#[property(
|
||||
get,
|
||||
set,
|
||||
nick = "Value",
|
||||
blurb = "The value",
|
||||
minimum = 0f64,
|
||||
maximum = 100f64,
|
||||
default = 0f64
|
||||
)]
|
||||
value: RefCell<f64>,
|
||||
|
||||
#[property(
|
||||
get,
|
||||
set,
|
||||
nick = "Thickness",
|
||||
blurb = "Thickness",
|
||||
minimum = 0f64,
|
||||
maximum = 100f64,
|
||||
default = 1f64
|
||||
)]
|
||||
thickness: RefCell<f64>,
|
||||
|
||||
#[property(get, set, nick = "Clockwise", blurb = "Clockwise", default = true)]
|
||||
clockwise: RefCell<bool>,
|
||||
|
||||
content: RefCell<Option<gtk::Widget>>,
|
||||
}
|
||||
|
||||
// This should match the default values from the ParamSpecs
|
||||
impl Default for CircProgPriv {
|
||||
fn default() -> Self {
|
||||
CircProgPriv {
|
||||
start_at: RefCell::new(0.0),
|
||||
value: RefCell::new(0.0),
|
||||
thickness: RefCell::new(1.0),
|
||||
clockwise: RefCell::new(true),
|
||||
content: RefCell::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for CircProgPriv {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
Self::derived_properties()
|
||||
pub struct CircProg {
|
||||
pub value: Cell<f64>,
|
||||
pub start_at: Cell<f64>,
|
||||
pub thickness: Cell<f64>,
|
||||
pub clockwise: Cell<bool>,
|
||||
pub fg_color: Cell<gdk::RGBA>,
|
||||
pub bg_color: Cell<gdk::RGBA>,
|
||||
}
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
match pspec.name() {
|
||||
"value" => {
|
||||
self.value.replace(value.get().unwrap());
|
||||
self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||
impl Default for CircProg {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
value: Cell::new(0.0),
|
||||
start_at: Cell::new(0.0),
|
||||
thickness: Cell::new(8.0),
|
||||
clockwise: Cell::new(true),
|
||||
fg_color: Cell::new(gdk::RGBA::new(1.0, 0.0, 0.0, 1.0)),
|
||||
bg_color: Cell::new(gdk::RGBA::new(0.0, 0.0, 0.0, 0.1)),
|
||||
}
|
||||
"thickness" => {
|
||||
self.thickness.replace(value.get().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for CircProg {
|
||||
const NAME: &'static str = "CircProg";
|
||||
type Type = super::CircProg;
|
||||
type ParentType = gtk4::Widget;
|
||||
}
|
||||
|
||||
impl ObjectImpl for CircProg {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let obj = self.obj();
|
||||
obj.add_css_class("circular-progress");
|
||||
}
|
||||
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
use once_cell::sync::Lazy;
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![
|
||||
glib::ParamSpecDouble::builder("value")
|
||||
.minimum(0.0)
|
||||
.maximum(100.0)
|
||||
.default_value(0.0)
|
||||
.build(),
|
||||
glib::ParamSpecDouble::builder("start-at")
|
||||
.minimum(0.0)
|
||||
.maximum(100.0)
|
||||
.default_value(0.0)
|
||||
.build(),
|
||||
glib::ParamSpecDouble::builder("thickness")
|
||||
.minimum(1.0)
|
||||
.maximum(50.0)
|
||||
.default_value(8.0)
|
||||
.build(),
|
||||
glib::ParamSpecBoolean::builder("clockwise").default_value(true).build(),
|
||||
glib::ParamSpecBoxed::builder::<gdk::RGBA>("fg-color").build(),
|
||||
glib::ParamSpecBoxed::builder::<gdk::RGBA>("bg-color").build(),
|
||||
]
|
||||
});
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, _: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
match pspec.name() {
|
||||
"value" => self.value.set(value.get().unwrap()),
|
||||
"start-at" => self.start_at.set(value.get().unwrap()),
|
||||
"thickness" => self.thickness.set(value.get().unwrap()),
|
||||
"clockwise" => self.clockwise.set(value.get().unwrap()),
|
||||
"fg-color" => self.fg_color.set(value.get().unwrap()),
|
||||
"bg-color" => self.bg_color.set(value.get().unwrap()),
|
||||
x => panic!("Tried to set inexistant property of CircProg: {}", x,),
|
||||
}
|
||||
"start-at" => {
|
||||
self.start_at.replace(value.get().unwrap());
|
||||
self.obj().queue_draw();
|
||||
}
|
||||
|
||||
fn property(&self, _: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
match pspec.name() {
|
||||
"value" => self.value.get().to_value(),
|
||||
"start-at" => self.start_at.get().to_value(),
|
||||
"thickness" => self.thickness.get().to_value(),
|
||||
"clockwise" => self.clockwise.get().to_value(),
|
||||
"fg-color" => self.fg_color.get().to_value(),
|
||||
"bg-color" => self.bg_color.get().to_value(),
|
||||
x => panic!("Tried to get inexistant property of CircProg: {}", x,),
|
||||
}
|
||||
"clockwise" => {
|
||||
self.clockwise.replace(value.get().unwrap());
|
||||
}
|
||||
x => panic!("Tried to set inexistant property of CircProg: {}", x,),
|
||||
}
|
||||
}
|
||||
|
||||
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
self.derived_property(id, pspec)
|
||||
}
|
||||
}
|
||||
|
||||
#[object_subclass]
|
||||
impl ObjectSubclass for CircProgPriv {
|
||||
type ParentType = gtk::Bin;
|
||||
type Type = CircProg;
|
||||
|
||||
const NAME: &'static str = "CircProg";
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.set_css_name("circular-progress");
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CircProg {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CircProg {
|
||||
pub fn new() -> Self {
|
||||
glib::Object::new::<Self>()
|
||||
}
|
||||
}
|
||||
|
||||
impl ContainerImpl for CircProgPriv {
|
||||
fn add(&self, widget: >k::Widget) {
|
||||
if let Some(content) = &*self.content.borrow() {
|
||||
// TODO: Handle this error when populating children widgets instead
|
||||
error_handling_ctx::print_error(anyhow!(
|
||||
"Error, trying to add multiple children to a circular-progress widget"
|
||||
));
|
||||
self.parent_remove(content);
|
||||
impl WidgetImpl for CircProg {
|
||||
fn measure(&self, _orientation: gtk4::Orientation, _for_size: i32) -> (i32, i32, i32, i32) {
|
||||
let min_size = 32;
|
||||
let natural_size = 64;
|
||||
(min_size, natural_size, -1, -1)
|
||||
}
|
||||
self.parent_add(widget);
|
||||
self.content.replace(Some(widget.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_widget_lowest_preferred_dimension(widget: >k::Widget) -> (i32, i32) {
|
||||
let preferred_width = widget.preferred_width();
|
||||
let preferred_height = widget.preferred_height();
|
||||
let min_lowest = i32::min(preferred_width.0, preferred_height.0);
|
||||
let natural_lowest = i32::min(preferred_width.1, preferred_height.1);
|
||||
(min_lowest, natural_lowest)
|
||||
}
|
||||
fn snapshot(&self, snapshot: >k4::Snapshot) {
|
||||
let value = self.value.get();
|
||||
let start_at = self.start_at.get();
|
||||
let thickness = self.thickness.get();
|
||||
let clockwise = self.clockwise.get();
|
||||
let fg_color = self.fg_color.get();
|
||||
let bg_color = self.bg_color.get();
|
||||
|
||||
impl BinImpl for CircProgPriv {}
|
||||
|
||||
impl WidgetImpl for CircProgPriv {
|
||||
// We overwrite preferred_* so that overflowing content from the children gets cropped
|
||||
// We return min(child_width, child_height)
|
||||
fn preferred_width(&self) -> (i32, i32) {
|
||||
let styles = self.obj().style_context();
|
||||
let margin = styles.margin(gtk::StateFlags::NORMAL);
|
||||
|
||||
if let Some(child) = &*self.content.borrow() {
|
||||
let (min_child, natural_child) = calc_widget_lowest_preferred_dimension(child);
|
||||
(
|
||||
min_child + margin.right as i32 + margin.left as i32,
|
||||
natural_child + margin.right as i32 + margin.left as i32,
|
||||
)
|
||||
} else {
|
||||
let empty_width =
|
||||
(2 * *self.thickness.borrow() as i32) + margin.right as i32 + margin.left as i32;
|
||||
(empty_width, empty_width)
|
||||
}
|
||||
}
|
||||
|
||||
fn preferred_width_for_height(&self, _height: i32) -> (i32, i32) {
|
||||
self.preferred_width()
|
||||
}
|
||||
|
||||
fn preferred_height(&self) -> (i32, i32) {
|
||||
let styles = self.obj().style_context();
|
||||
let margin = styles.margin(gtk::StateFlags::NORMAL);
|
||||
|
||||
if let Some(child) = &*self.content.borrow() {
|
||||
let (min_child, natural_child) = calc_widget_lowest_preferred_dimension(child);
|
||||
(
|
||||
min_child + margin.bottom as i32 + margin.top as i32,
|
||||
natural_child + margin.bottom as i32 + margin.top as i32,
|
||||
)
|
||||
} else {
|
||||
let empty_height =
|
||||
(2 * *self.thickness.borrow() as i32) + margin.right as i32 + margin.left as i32;
|
||||
(empty_height, empty_height)
|
||||
}
|
||||
}
|
||||
|
||||
fn preferred_height_for_width(&self, _width: i32) -> (i32, i32) {
|
||||
self.preferred_height()
|
||||
}
|
||||
|
||||
fn draw(&self, cr: &cairo::Context) -> glib::Propagation {
|
||||
let res: Result<()> = (|| {
|
||||
let value = *self.value.borrow();
|
||||
let start_at = *self.start_at.borrow();
|
||||
let thickness = *self.thickness.borrow();
|
||||
let clockwise = *self.clockwise.borrow();
|
||||
|
||||
let styles = self.obj().style_context();
|
||||
let margin = styles.margin(gtk::StateFlags::NORMAL);
|
||||
let margin_start = self.obj().margin_start() as f64;
|
||||
let margin_end = self.obj().margin_end() as f64;
|
||||
let margin_top = self.obj().margin_top() as f64;
|
||||
let margin_bottom = self.obj().margin_bottom() as f64;
|
||||
// Padding is not supported yet
|
||||
let fg_color: gdk::RGBA = styles.color(gtk::StateFlags::NORMAL);
|
||||
let bg_color: gdk::RGBA = styles
|
||||
.style_property_for_state("background-color", gtk::StateFlags::NORMAL)
|
||||
.get()?;
|
||||
|
||||
let (start_angle, end_angle) = if clockwise {
|
||||
(0.0, perc_to_rad(value))
|
||||
} else {
|
||||
@@ -211,12 +129,20 @@ impl WidgetImpl for CircProgPriv {
|
||||
let total_height = self.obj().allocated_height() as f64;
|
||||
let center = (total_width / 2.0, total_height / 2.0);
|
||||
|
||||
let circle_width = total_width - margin.left as f64 - margin.right as f64;
|
||||
let circle_height = total_height - margin.top as f64 - margin.bottom as f64;
|
||||
let circle_width = total_width - margin_start - margin_end;
|
||||
let circle_height = total_height - margin_top - margin_bottom;
|
||||
let outer_ring = f64::min(circle_width, circle_height) / 2.0;
|
||||
let inner_ring = (f64::min(circle_width, circle_height) / 2.0) - thickness;
|
||||
|
||||
cr.save()?;
|
||||
// Snapshot Cairo node
|
||||
let cr = snapshot.append_cairo(&graphene::Rect::new(
|
||||
0.0_f32,
|
||||
0.0_f32,
|
||||
total_width as f32,
|
||||
total_height as f32,
|
||||
));
|
||||
|
||||
cr.save().unwrap();
|
||||
|
||||
// Centering
|
||||
cr.translate(center.0, center.1);
|
||||
@@ -226,45 +152,45 @@ impl WidgetImpl for CircProgPriv {
|
||||
// Background Ring
|
||||
cr.move_to(center.0, center.1);
|
||||
cr.arc(center.0, center.1, outer_ring, 0.0, perc_to_rad(100.0));
|
||||
cr.set_source_rgba(bg_color.red(), bg_color.green(), bg_color.blue(), bg_color.alpha());
|
||||
cr.set_source_rgba(
|
||||
bg_color.red().into(),
|
||||
bg_color.green().into(),
|
||||
bg_color.blue().into(),
|
||||
bg_color.alpha().into(),
|
||||
);
|
||||
cr.move_to(center.0, center.1);
|
||||
cr.arc(center.0, center.1, inner_ring, 0.0, perc_to_rad(100.0));
|
||||
cr.set_fill_rule(cairo::FillRule::EvenOdd); // Substract one circle from the other
|
||||
cr.fill()?;
|
||||
cr.fill().unwrap();
|
||||
|
||||
// Foreground Ring
|
||||
cr.move_to(center.0, center.1);
|
||||
cr.arc(center.0, center.1, outer_ring, start_angle, end_angle);
|
||||
cr.set_source_rgba(fg_color.red(), fg_color.green(), fg_color.blue(), fg_color.alpha());
|
||||
cr.set_source_rgba(
|
||||
fg_color.red().into(),
|
||||
fg_color.green().into(),
|
||||
fg_color.blue().into(),
|
||||
fg_color.alpha().into(),
|
||||
);
|
||||
cr.move_to(center.0, center.1);
|
||||
cr.arc(center.0, center.1, inner_ring, start_angle, end_angle);
|
||||
cr.set_fill_rule(cairo::FillRule::EvenOdd); // Substract one circle from the other
|
||||
cr.fill()?;
|
||||
cr.restore()?;
|
||||
cr.fill().unwrap();
|
||||
|
||||
// Draw the children widget, clipping it to the inside
|
||||
if let Some(child) = &*self.content.borrow() {
|
||||
cr.save()?;
|
||||
cr.restore().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Center circular clip
|
||||
cr.arc(center.0, center.1, inner_ring + 1.0, 0.0, perc_to_rad(100.0));
|
||||
cr.set_source_rgba(bg_color.red(), 0.0, 0.0, bg_color.alpha());
|
||||
cr.clip();
|
||||
glib::wrapper! {
|
||||
pub struct CircProg(ObjectSubclass<imp::CircProg>)
|
||||
@extends gtk4::Widget,
|
||||
@implements gtk4::Accessible, gtk4::Actionable, gtk4::Buildable, gtk4::ConstraintTarget;
|
||||
}
|
||||
|
||||
// Children widget
|
||||
self.obj().propagate_draw(child, cr);
|
||||
|
||||
cr.reset_clip();
|
||||
cr.restore()?;
|
||||
}
|
||||
Ok(())
|
||||
})();
|
||||
|
||||
if let Err(error) = res {
|
||||
error_handling_ctx::print_error(error)
|
||||
};
|
||||
|
||||
glib::Propagation::Proceed
|
||||
impl CircProg {
|
||||
pub fn new() -> Self {
|
||||
Object::builder().build()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,349 +1,349 @@
|
||||
use std::{cell::RefCell, collections::VecDeque};
|
||||
// https://www.figuiere.net/technotes/notes/tn002/
|
||||
// https://github.com/gtk-rs/examples/blob/master/src/bin/listbox_model.rs
|
||||
use anyhow::{anyhow, Result};
|
||||
use gtk::glib::{self, object_subclass, wrapper, Properties};
|
||||
use gtk::{cairo, gdk, prelude::*, subclass::prelude::*};
|
||||
// use std::{cell::RefCell, collections::VecDeque};
|
||||
// // https://www.figuiere.net/technotes/notes/tn002/
|
||||
// // https://github.com/gtk-rs/examples/blob/master/src/bin/listbox_model.rs
|
||||
// use anyhow::{anyhow, Result};
|
||||
// use gtk4::glib::{self, object_subclass, wrapper, Properties};
|
||||
// use gtk4::{cairo, gdk, prelude::*, subclass::prelude::*};
|
||||
|
||||
use crate::error_handling_ctx;
|
||||
// use crate::error_handling_ctx;
|
||||
|
||||
// This widget shouldn't be a Bin/Container but I've not been
|
||||
// able to subclass just a gtk::Widget
|
||||
wrapper! {
|
||||
pub struct Graph(ObjectSubclass<GraphPriv>)
|
||||
@extends gtk::Bin, gtk::Container, gtk::Widget;
|
||||
}
|
||||
// // This widget shouldn't be a Bin/Container but I've not been
|
||||
// // able to subclass just a gtk4::Widget
|
||||
// wrapper! {
|
||||
// pub struct Graph(ObjectSubclass<GraphPriv>)
|
||||
// @extends gtk4::Bin, gtk4::Container, gtk4::Widget;
|
||||
// }
|
||||
|
||||
#[derive(Properties)]
|
||||
#[properties(wrapper_type = Graph)]
|
||||
pub struct GraphPriv {
|
||||
#[property(get, set, nick = "Value", blurb = "The value", minimum = 0f64, maximum = f64::MAX, default = 0f64)]
|
||||
value: RefCell<f64>,
|
||||
// #[derive(Properties)]
|
||||
// #[properties(wrapper_type = Graph)]
|
||||
// pub struct GraphPriv {
|
||||
// #[property(get, set, nick = "Value", blurb = "The value", minimum = 0f64, maximum = f64::MAX, default = 0f64)]
|
||||
// value: RefCell<f64>,
|
||||
|
||||
#[property(get, set, nick = "Thickness", blurb = "The Thickness", minimum = 0f64, maximum = f64::MAX, default = 1f64)]
|
||||
thickness: RefCell<f64>,
|
||||
// #[property(get, set, nick = "Thickness", blurb = "The Thickness", minimum = 0f64, maximum = f64::MAX, default = 1f64)]
|
||||
// thickness: RefCell<f64>,
|
||||
|
||||
#[property(get, set, nick = "Line Style", blurb = "The Line Style", default = "miter")]
|
||||
line_style: RefCell<String>,
|
||||
// #[property(get, set, nick = "Line Style", blurb = "The Line Style", default = "miter")]
|
||||
// line_style: RefCell<String>,
|
||||
|
||||
#[property(get, set, nick = "Maximum Value", blurb = "The Maximum Value", minimum = 0f64, maximum = f64::MAX, default = 100f64)]
|
||||
min: RefCell<f64>,
|
||||
// #[property(get, set, nick = "Maximum Value", blurb = "The Maximum Value", minimum = 0f64, maximum = f64::MAX, default = 100f64)]
|
||||
// min: RefCell<f64>,
|
||||
|
||||
#[property(get, set, nick = "Minumum Value", blurb = "The Minimum Value", minimum = 0f64, maximum = f64::MAX, default = 0f64)]
|
||||
max: RefCell<f64>,
|
||||
// #[property(get, set, nick = "Minumum Value", blurb = "The Minimum Value", minimum = 0f64, maximum = f64::MAX, default = 0f64)]
|
||||
// max: RefCell<f64>,
|
||||
|
||||
#[property(get, set, nick = "Dynamic", blurb = "If it is dynamic", default = true)]
|
||||
dynamic: RefCell<bool>,
|
||||
// #[property(get, set, nick = "Dynamic", blurb = "If it is dynamic", default = true)]
|
||||
// dynamic: RefCell<bool>,
|
||||
|
||||
#[property(get, set, nick = "Time Range", blurb = "The Time Range", minimum = 0u64, maximum = u64::MAX, default = 10u64)]
|
||||
time_range: RefCell<u64>,
|
||||
// #[property(get, set, nick = "Time Range", blurb = "The Time Range", minimum = 0u64, maximum = u64::MAX, default = 10u64)]
|
||||
// time_range: RefCell<u64>,
|
||||
|
||||
#[property(get, set, nick = "Flip X", blurb = "Flip the x axis", default = true)]
|
||||
flip_x: RefCell<bool>,
|
||||
#[property(get, set, nick = "Flip Y", blurb = "Flip the y axis", default = true)]
|
||||
flip_y: RefCell<bool>,
|
||||
#[property(get, set, nick = "Vertical", blurb = "Exchange the x and y axes", default = false)]
|
||||
vertical: RefCell<bool>,
|
||||
// #[property(get, set, nick = "Flip X", blurb = "Flip the x axis", default = true)]
|
||||
// flip_x: RefCell<bool>,
|
||||
// #[property(get, set, nick = "Flip Y", blurb = "Flip the y axis", default = true)]
|
||||
// flip_y: RefCell<bool>,
|
||||
// #[property(get, set, nick = "Vertical", blurb = "Exchange the x and y axes", default = false)]
|
||||
// vertical: RefCell<bool>,
|
||||
|
||||
history: RefCell<VecDeque<(std::time::Instant, f64)>>,
|
||||
extra_point: RefCell<Option<(std::time::Instant, f64)>>,
|
||||
last_updated_at: RefCell<std::time::Instant>,
|
||||
}
|
||||
// history: RefCell<VecDeque<(std::time::Instant, f64)>>,
|
||||
// extra_point: RefCell<Option<(std::time::Instant, f64)>>,
|
||||
// last_updated_at: RefCell<std::time::Instant>,
|
||||
// }
|
||||
|
||||
impl Default for GraphPriv {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
value: RefCell::new(0.0),
|
||||
thickness: RefCell::new(1.0),
|
||||
line_style: RefCell::new("miter".to_string()),
|
||||
min: RefCell::new(0.0),
|
||||
max: RefCell::new(100.0),
|
||||
dynamic: RefCell::new(true),
|
||||
time_range: RefCell::new(10),
|
||||
flip_x: RefCell::new(true),
|
||||
flip_y: RefCell::new(true),
|
||||
vertical: RefCell::new(false),
|
||||
history: RefCell::new(VecDeque::new()),
|
||||
extra_point: RefCell::new(None),
|
||||
last_updated_at: RefCell::new(std::time::Instant::now()),
|
||||
}
|
||||
}
|
||||
}
|
||||
// impl Default for GraphPriv {
|
||||
// fn default() -> Self {
|
||||
// Self {
|
||||
// value: RefCell::new(0.0),
|
||||
// thickness: RefCell::new(1.0),
|
||||
// line_style: RefCell::new("miter".to_string()),
|
||||
// min: RefCell::new(0.0),
|
||||
// max: RefCell::new(100.0),
|
||||
// dynamic: RefCell::new(true),
|
||||
// time_range: RefCell::new(10),
|
||||
// flip_x: RefCell::new(true),
|
||||
// flip_y: RefCell::new(true),
|
||||
// vertical: RefCell::new(false),
|
||||
// history: RefCell::new(VecDeque::new()),
|
||||
// extra_point: RefCell::new(None),
|
||||
// last_updated_at: RefCell::new(std::time::Instant::now()),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl GraphPriv {
|
||||
// Updates the history, removing points ouside the range
|
||||
fn update_history(&self, v: (std::time::Instant, f64)) {
|
||||
let mut history = self.history.borrow_mut();
|
||||
let mut last_value = self.extra_point.borrow_mut();
|
||||
let mut last_updated_at = self.last_updated_at.borrow_mut();
|
||||
*last_updated_at = std::time::Instant::now();
|
||||
// impl GraphPriv {
|
||||
// // Updates the history, removing points ouside the range
|
||||
// fn update_history(&self, v: (std::time::Instant, f64)) {
|
||||
// let mut history = self.history.borrow_mut();
|
||||
// let mut last_value = self.extra_point.borrow_mut();
|
||||
// let mut last_updated_at = self.last_updated_at.borrow_mut();
|
||||
// *last_updated_at = std::time::Instant::now();
|
||||
|
||||
while let Some(entry) = history.front() {
|
||||
if last_updated_at.duration_since(entry.0).as_millis() as u64
|
||||
> *self.time_range.borrow()
|
||||
{
|
||||
*last_value = history.pop_front();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
history.push_back(v);
|
||||
}
|
||||
/**
|
||||
* Receives normalized (0-1) coordinates `x` and `y` and convert them to the
|
||||
* point on the widget.
|
||||
*/
|
||||
fn value_to_point(&self, width: f64, height: f64, x: f64, y: f64) -> (f64, f64) {
|
||||
let x = if *self.flip_x.borrow() { 1.0 - x } else { x };
|
||||
let y = if *self.flip_y.borrow() { 1.0 - y } else { y };
|
||||
let (x, y) = if *self.vertical.borrow() { (y, x) } else { (x, y) };
|
||||
(width * x, height * y)
|
||||
}
|
||||
}
|
||||
// while let Some(entry) = history.front() {
|
||||
// if last_updated_at.duration_since(entry.0).as_millis() as u64
|
||||
// > *self.time_range.borrow()
|
||||
// {
|
||||
// *last_value = history.pop_front();
|
||||
// } else {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// history.push_back(v);
|
||||
// }
|
||||
// /**
|
||||
// * Receives normalized (0-1) coordinates `x` and `y` and convert them to the
|
||||
// * point on the widget.
|
||||
// */
|
||||
// fn value_to_point(&self, width: f64, height: f64, x: f64, y: f64) -> (f64, f64) {
|
||||
// let x = if *self.flip_x.borrow() { 1.0 - x } else { x };
|
||||
// let y = if *self.flip_y.borrow() { 1.0 - y } else { y };
|
||||
// let (x, y) = if *self.vertical.borrow() { (y, x) } else { (x, y) };
|
||||
// (width * x, height * y)
|
||||
// }
|
||||
// }
|
||||
|
||||
impl ObjectImpl for GraphPriv {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
Self::derived_properties()
|
||||
}
|
||||
// impl ObjectImpl for GraphPriv {
|
||||
// fn properties() -> &'static [glib::ParamSpec] {
|
||||
// Self::derived_properties()
|
||||
// }
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
match pspec.name() {
|
||||
"value" => {
|
||||
let value = value.get().unwrap();
|
||||
self.value.replace(value);
|
||||
self.update_history((std::time::Instant::now(), value));
|
||||
self.obj().queue_draw();
|
||||
}
|
||||
"thickness" => {
|
||||
self.thickness.replace(value.get().unwrap());
|
||||
}
|
||||
"max" => {
|
||||
self.max.replace(value.get().unwrap());
|
||||
}
|
||||
"min" => {
|
||||
self.min.replace(value.get().unwrap());
|
||||
}
|
||||
"dynamic" => {
|
||||
self.dynamic.replace(value.get().unwrap());
|
||||
}
|
||||
"time-range" => {
|
||||
self.time_range.replace(value.get().unwrap());
|
||||
}
|
||||
"line-style" => {
|
||||
self.line_style.replace(value.get().unwrap());
|
||||
}
|
||||
"flip-x" => {
|
||||
self.flip_x.replace(value.get().unwrap());
|
||||
}
|
||||
"flip-y" => {
|
||||
self.flip_y.replace(value.get().unwrap());
|
||||
}
|
||||
"vertical" => {
|
||||
self.vertical.replace(value.get().unwrap());
|
||||
}
|
||||
x => panic!("Tried to set inexistant property of Graph: {}", x,),
|
||||
}
|
||||
}
|
||||
// fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
// match pspec.name() {
|
||||
// "value" => {
|
||||
// let value = value.get().unwrap();
|
||||
// self.value.replace(value);
|
||||
// self.update_history((std::time::Instant::now(), value));
|
||||
// self.obj().queue_draw();
|
||||
// }
|
||||
// "thickness" => {
|
||||
// self.thickness.replace(value.get().unwrap());
|
||||
// }
|
||||
// "max" => {
|
||||
// self.max.replace(value.get().unwrap());
|
||||
// }
|
||||
// "min" => {
|
||||
// self.min.replace(value.get().unwrap());
|
||||
// }
|
||||
// "dynamic" => {
|
||||
// self.dynamic.replace(value.get().unwrap());
|
||||
// }
|
||||
// "time-range" => {
|
||||
// self.time_range.replace(value.get().unwrap());
|
||||
// }
|
||||
// "line-style" => {
|
||||
// self.line_style.replace(value.get().unwrap());
|
||||
// }
|
||||
// "flip-x" => {
|
||||
// self.flip_x.replace(value.get().unwrap());
|
||||
// }
|
||||
// "flip-y" => {
|
||||
// self.flip_y.replace(value.get().unwrap());
|
||||
// }
|
||||
// "vertical" => {
|
||||
// self.vertical.replace(value.get().unwrap());
|
||||
// }
|
||||
// x => panic!("Tried to set inexistant property of Graph: {}", x,),
|
||||
// }
|
||||
// }
|
||||
|
||||
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
self.derived_property(id, pspec)
|
||||
}
|
||||
}
|
||||
// fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
// self.derived_property(id, pspec)
|
||||
// }
|
||||
// }
|
||||
|
||||
#[object_subclass]
|
||||
impl ObjectSubclass for GraphPriv {
|
||||
type ParentType = gtk::Bin;
|
||||
type Type = Graph;
|
||||
// #[object_subclass]
|
||||
// impl ObjectSubclass for GraphPriv {
|
||||
// type ParentType = gtk4::Bin;
|
||||
// type Type = Graph;
|
||||
|
||||
const NAME: &'static str = "Graph";
|
||||
// const NAME: &'static str = "Graph";
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.set_css_name("graph");
|
||||
}
|
||||
}
|
||||
// fn class_init(klass: &mut Self::Class) {
|
||||
// klass.set_css_name("graph");
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Default for Graph {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
// impl Default for Graph {
|
||||
// fn default() -> Self {
|
||||
// Self::new()
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Graph {
|
||||
pub fn new() -> Self {
|
||||
glib::Object::new::<Self>()
|
||||
}
|
||||
}
|
||||
// impl Graph {
|
||||
// pub fn new() -> Self {
|
||||
// glib::Object::new::<Self>()
|
||||
// }
|
||||
// }
|
||||
|
||||
impl ContainerImpl for GraphPriv {
|
||||
fn add(&self, _widget: >k::Widget) {
|
||||
error_handling_ctx::print_error(anyhow!("Error, Graph widget shoudln't have any children"));
|
||||
}
|
||||
}
|
||||
// impl ContainerImpl for GraphPriv {
|
||||
// fn add(&self, _widget: >k4::Widget) {
|
||||
// error_handling_ctx::print_error(anyhow!("Error, Graph widget shoudln't have any children"));
|
||||
// }
|
||||
// }
|
||||
|
||||
impl BinImpl for GraphPriv {}
|
||||
impl WidgetImpl for GraphPriv {
|
||||
fn preferred_width(&self) -> (i32, i32) {
|
||||
let thickness = *self.thickness.borrow() as i32;
|
||||
(thickness, thickness)
|
||||
}
|
||||
// impl BinImpl for GraphPriv {}
|
||||
// impl WidgetImpl for GraphPriv {
|
||||
// fn preferred_width(&self) -> (i32, i32) {
|
||||
// let thickness = *self.thickness.borrow() as i32;
|
||||
// (thickness, thickness)
|
||||
// }
|
||||
|
||||
fn preferred_width_for_height(&self, height: i32) -> (i32, i32) {
|
||||
(height, height)
|
||||
}
|
||||
// fn preferred_width_for_height(&self, height: i32) -> (i32, i32) {
|
||||
// (height, height)
|
||||
// }
|
||||
|
||||
fn preferred_height(&self) -> (i32, i32) {
|
||||
let thickness = *self.thickness.borrow() as i32;
|
||||
(thickness, thickness)
|
||||
}
|
||||
// fn preferred_height(&self) -> (i32, i32) {
|
||||
// let thickness = *self.thickness.borrow() as i32;
|
||||
// (thickness, thickness)
|
||||
// }
|
||||
|
||||
fn preferred_height_for_width(&self, width: i32) -> (i32, i32) {
|
||||
(width, width)
|
||||
}
|
||||
// fn preferred_height_for_width(&self, width: i32) -> (i32, i32) {
|
||||
// (width, width)
|
||||
// }
|
||||
|
||||
fn draw(&self, cr: &cairo::Context) -> glib::Propagation {
|
||||
let res: Result<()> = (|| {
|
||||
let history = &*self.history.borrow();
|
||||
let extra_point = *self.extra_point.borrow();
|
||||
// fn draw(&self, cr: &cairo::Context) -> glib::Propagation {
|
||||
// let res: Result<()> = (|| {
|
||||
// let history = &*self.history.borrow();
|
||||
// let extra_point = *self.extra_point.borrow();
|
||||
|
||||
// Calculate the max value
|
||||
let (min, max) = {
|
||||
let mut max = *self.max.borrow();
|
||||
let min = *self.min.borrow();
|
||||
let dynamic = *self.dynamic.borrow();
|
||||
if dynamic {
|
||||
// Check for points higher than max
|
||||
for (_, value) in history {
|
||||
if *value > max {
|
||||
max = *value;
|
||||
}
|
||||
}
|
||||
if let Some((_, value)) = extra_point {
|
||||
if value > max {
|
||||
max = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
(min, max)
|
||||
};
|
||||
// // Calculate the max value
|
||||
// let (min, max) = {
|
||||
// let mut max = *self.max.borrow();
|
||||
// let min = *self.min.borrow();
|
||||
// let dynamic = *self.dynamic.borrow();
|
||||
// if dynamic {
|
||||
// // Check for points higher than max
|
||||
// for (_, value) in history {
|
||||
// if *value > max {
|
||||
// max = *value;
|
||||
// }
|
||||
// }
|
||||
// if let Some((_, value)) = extra_point {
|
||||
// if value > max {
|
||||
// max = value;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// (min, max)
|
||||
// };
|
||||
|
||||
let styles = self.obj().style_context();
|
||||
let (margin_top, margin_right, margin_bottom, margin_left) = {
|
||||
let margin = styles.margin(gtk::StateFlags::NORMAL);
|
||||
(margin.top as f64, margin.right as f64, margin.bottom as f64, margin.left as f64)
|
||||
};
|
||||
let width = self.obj().allocated_width() as f64 - margin_left - margin_right;
|
||||
let height = self.obj().allocated_height() as f64 - margin_top - margin_bottom;
|
||||
// let styles = self.obj().style_context();
|
||||
// let (margin_top, margin_right, margin_bottom, margin_left) = {
|
||||
// let margin = styles.margin(gtk4::StateFlags::NORMAL);
|
||||
// (margin.top as f64, margin.right as f64, margin.bottom as f64, margin.left as f64)
|
||||
// };
|
||||
// let width = self.obj().allocated_width() as f64 - margin_left - margin_right;
|
||||
// let height = self.obj().allocated_height() as f64 - margin_top - margin_bottom;
|
||||
|
||||
// Calculate graph points once
|
||||
// Separating this into another function would require pasing a
|
||||
// GraphPriv that would hide interior mutability
|
||||
let points = {
|
||||
let value_range = max - min;
|
||||
let time_range = *self.time_range.borrow() as f64;
|
||||
let last_updated_at = self.last_updated_at.borrow();
|
||||
let mut points = history
|
||||
.iter()
|
||||
.map(|(instant, value)| {
|
||||
let t = last_updated_at.duration_since(*instant).as_millis() as f64;
|
||||
self.value_to_point(
|
||||
width,
|
||||
height,
|
||||
t / time_range,
|
||||
(value - min) / value_range,
|
||||
)
|
||||
})
|
||||
.collect::<VecDeque<(f64, f64)>>();
|
||||
// // Calculate graph points once
|
||||
// // Separating this into another function would require pasing a
|
||||
// // GraphPriv that would hide interior mutability
|
||||
// let points = {
|
||||
// let value_range = max - min;
|
||||
// let time_range = *self.time_range.borrow() as f64;
|
||||
// let last_updated_at = self.last_updated_at.borrow();
|
||||
// let mut points = history
|
||||
// .iter()
|
||||
// .map(|(instant, value)| {
|
||||
// let t = last_updated_at.duration_since(*instant).as_millis() as f64;
|
||||
// self.value_to_point(
|
||||
// width,
|
||||
// height,
|
||||
// t / time_range,
|
||||
// (value - min) / value_range,
|
||||
// )
|
||||
// })
|
||||
// .collect::<VecDeque<(f64, f64)>>();
|
||||
|
||||
// Aad an extra point outside of the graph to extend the line to the left
|
||||
if let Some((instant, value)) = extra_point {
|
||||
let t = last_updated_at.duration_since(instant).as_millis() as f64;
|
||||
let (x, y) = self.value_to_point(
|
||||
width,
|
||||
height,
|
||||
(t - time_range) / time_range,
|
||||
(value - min) / value_range,
|
||||
);
|
||||
points.push_front(if *self.vertical.borrow() { (x, -y) } else { (-x, y) });
|
||||
}
|
||||
points
|
||||
};
|
||||
// // Aad an extra point outside of the graph to extend the line to the left
|
||||
// if let Some((instant, value)) = extra_point {
|
||||
// let t = last_updated_at.duration_since(instant).as_millis() as f64;
|
||||
// let (x, y) = self.value_to_point(
|
||||
// width,
|
||||
// height,
|
||||
// (t - time_range) / time_range,
|
||||
// (value - min) / value_range,
|
||||
// );
|
||||
// points.push_front(if *self.vertical.borrow() { (x, -y) } else { (-x, y) });
|
||||
// }
|
||||
// points
|
||||
// };
|
||||
|
||||
// Actually draw the graph
|
||||
cr.save()?;
|
||||
cr.translate(margin_left, margin_top);
|
||||
cr.rectangle(0.0, 0.0, width, height);
|
||||
cr.clip();
|
||||
// // Actually draw the graph
|
||||
// cr.save()?;
|
||||
// cr.translate(margin_left, margin_top);
|
||||
// cr.rectangle(0.0, 0.0, width, height);
|
||||
// cr.clip();
|
||||
|
||||
// Draw Background
|
||||
let bg_color: gdk::RGBA = styles
|
||||
.style_property_for_state("background-color", gtk::StateFlags::NORMAL)
|
||||
.get()?;
|
||||
if bg_color.alpha() > 0.0 {
|
||||
if let Some(first_point) = points.front() {
|
||||
cr.line_to(first_point.0, height + margin_bottom);
|
||||
}
|
||||
for (x, y) in points.iter() {
|
||||
cr.line_to(*x, *y);
|
||||
}
|
||||
cr.line_to(width, height);
|
||||
// // Draw Background
|
||||
// let bg_color: gdk::RGBA = styles
|
||||
// .style_property_for_state("background-color", gtk4::StateFlags::NORMAL)
|
||||
// .get()?;
|
||||
// if bg_color.alpha() > 0.0 {
|
||||
// if let Some(first_point) = points.front() {
|
||||
// cr.line_to(first_point.0, height + margin_bottom);
|
||||
// }
|
||||
// for (x, y) in points.iter() {
|
||||
// cr.line_to(*x, *y);
|
||||
// }
|
||||
// cr.line_to(width, height);
|
||||
|
||||
cr.set_source_rgba(
|
||||
bg_color.red(),
|
||||
bg_color.green(),
|
||||
bg_color.blue(),
|
||||
bg_color.alpha(),
|
||||
);
|
||||
cr.fill()?;
|
||||
}
|
||||
// cr.set_source_rgba(
|
||||
// bg_color.red(),
|
||||
// bg_color.green(),
|
||||
// bg_color.blue(),
|
||||
// bg_color.alpha(),
|
||||
// );
|
||||
// cr.fill()?;
|
||||
// }
|
||||
|
||||
// Draw Line
|
||||
let line_color: gdk::RGBA = styles.color(gtk::StateFlags::NORMAL);
|
||||
let thickness = *self.thickness.borrow();
|
||||
if line_color.alpha() > 0.0 && thickness > 0.0 {
|
||||
for (x, y) in points.iter() {
|
||||
cr.line_to(*x, *y);
|
||||
}
|
||||
// // Draw Line
|
||||
// let line_color: gdk::RGBA = styles.color(gtk4::StateFlags::NORMAL);
|
||||
// let thickness = *self.thickness.borrow();
|
||||
// if line_color.alpha() > 0.0 && thickness > 0.0 {
|
||||
// for (x, y) in points.iter() {
|
||||
// cr.line_to(*x, *y);
|
||||
// }
|
||||
|
||||
let line_style = &*self.line_style.borrow();
|
||||
apply_line_style(line_style.as_str(), cr)?;
|
||||
cr.set_line_width(thickness);
|
||||
cr.set_source_rgba(
|
||||
line_color.red(),
|
||||
line_color.green(),
|
||||
line_color.blue(),
|
||||
line_color.alpha(),
|
||||
);
|
||||
cr.stroke()?;
|
||||
}
|
||||
// let line_style = &*self.line_style.borrow();
|
||||
// apply_line_style(line_style.as_str(), cr)?;
|
||||
// cr.set_line_width(thickness);
|
||||
// cr.set_source_rgba(
|
||||
// line_color.red(),
|
||||
// line_color.green(),
|
||||
// line_color.blue(),
|
||||
// line_color.alpha(),
|
||||
// );
|
||||
// cr.stroke()?;
|
||||
// }
|
||||
|
||||
cr.reset_clip();
|
||||
cr.restore()?;
|
||||
Ok(())
|
||||
})();
|
||||
// cr.reset_clip();
|
||||
// cr.restore()?;
|
||||
// Ok(())
|
||||
// })();
|
||||
|
||||
if let Err(error) = res {
|
||||
error_handling_ctx::print_error(error)
|
||||
};
|
||||
// if let Err(error) = res {
|
||||
// error_handling_ctx::print_error(error)
|
||||
// };
|
||||
|
||||
glib::Propagation::Proceed
|
||||
}
|
||||
}
|
||||
// glib::Propagation::Proceed
|
||||
// }
|
||||
// }
|
||||
|
||||
fn apply_line_style(style: &str, cr: &cairo::Context) -> Result<()> {
|
||||
match style {
|
||||
"miter" => {
|
||||
cr.set_line_cap(cairo::LineCap::Butt);
|
||||
cr.set_line_join(cairo::LineJoin::Miter);
|
||||
}
|
||||
"bevel" => {
|
||||
cr.set_line_cap(cairo::LineCap::Square);
|
||||
cr.set_line_join(cairo::LineJoin::Bevel);
|
||||
}
|
||||
"round" => {
|
||||
cr.set_line_cap(cairo::LineCap::Round);
|
||||
cr.set_line_join(cairo::LineJoin::Round);
|
||||
}
|
||||
_ => Err(anyhow!("Error, the value: {} for atribute join is not valid", style))?,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
// fn apply_line_style(style: &str, cr: &cairo::Context) -> Result<()> {
|
||||
// match style {
|
||||
// "miter" => {
|
||||
// cr.set_line_cap(cairo::LineCap::Butt);
|
||||
// cr.set_line_join(cairo::LineJoin::Miter);
|
||||
// }
|
||||
// "bevel" => {
|
||||
// cr.set_line_cap(cairo::LineCap::Square);
|
||||
// cr.set_line_join(cairo::LineJoin::Bevel);
|
||||
// }
|
||||
// "round" => {
|
||||
// cr.set_line_cap(cairo::LineCap::Round);
|
||||
// cr.set_line_join(cairo::LineJoin::Round);
|
||||
// }
|
||||
// _ => Err(anyhow!("Error, the value: {} for atribute join is not valid", style))?,
|
||||
// };
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
@@ -4,4 +4,3 @@ pub mod graph;
|
||||
pub mod transform;
|
||||
pub mod widget_definitions;
|
||||
pub mod widget_definitions_helper;
|
||||
pub mod window;
|
||||
|
||||
@@ -1,214 +1,214 @@
|
||||
use crate::window::coords::NumWithUnit;
|
||||
use anyhow::{anyhow, Result};
|
||||
use gtk::glib::{self, object_subclass, wrapper, Properties};
|
||||
use gtk::{prelude::*, subclass::prelude::*};
|
||||
use std::{cell::RefCell, str::FromStr};
|
||||
// use crate::window::coords::NumWithUnit;
|
||||
// use anyhow::{anyhow, Result};
|
||||
// use gtk4::glib::{self, object_subclass, wrapper, Properties};
|
||||
// use gtk4::{prelude::*, subclass::prelude::*};
|
||||
// use std::{cell::RefCell, str::FromStr};
|
||||
|
||||
use crate::error_handling_ctx;
|
||||
// use crate::error_handling_ctx;
|
||||
|
||||
wrapper! {
|
||||
pub struct Transform(ObjectSubclass<TransformPriv>)
|
||||
@extends gtk::Bin, gtk::Container, gtk::Widget;
|
||||
}
|
||||
// wrapper! {
|
||||
// pub struct Transform(ObjectSubclass<TransformPriv>)
|
||||
// @extends gtk4::Bin, gtk4::Container, gtk4::Widget;
|
||||
// }
|
||||
|
||||
#[derive(Properties)]
|
||||
#[properties(wrapper_type = Transform)]
|
||||
pub struct TransformPriv {
|
||||
#[property(get, set, nick = "Rotate", blurb = "The Rotation", minimum = f64::MIN, maximum = f64::MAX, default = 0f64)]
|
||||
rotate: RefCell<f64>,
|
||||
// #[derive(Properties)]
|
||||
// #[properties(wrapper_type = Transform)]
|
||||
// pub struct TransformPriv {
|
||||
// #[property(get, set, nick = "Rotate", blurb = "The Rotation", minimum = f64::MIN, maximum = f64::MAX, default = 0f64)]
|
||||
// rotate: RefCell<f64>,
|
||||
|
||||
#[property(get, set, nick = "Transform-Origin X", blurb = "X coordinate (%/px) for the Transform-Origin", default = None)]
|
||||
transform_origin_x: RefCell<Option<String>>,
|
||||
// #[property(get, set, nick = "Transform-Origin X", blurb = "X coordinate (%/px) for the Transform-Origin", default = None)]
|
||||
// transform_origin_x: RefCell<Option<String>>,
|
||||
|
||||
#[property(get, set, nick = "Transform-Origin Y", blurb = "Y coordinate (%/px) for the Transform-Origin", default = None)]
|
||||
transform_origin_y: RefCell<Option<String>>,
|
||||
// #[property(get, set, nick = "Transform-Origin Y", blurb = "Y coordinate (%/px) for the Transform-Origin", default = None)]
|
||||
// transform_origin_y: RefCell<Option<String>>,
|
||||
|
||||
#[property(get, set, nick = "Translate x", blurb = "The X Translation", default = None)]
|
||||
translate_x: RefCell<Option<String>>,
|
||||
// #[property(get, set, nick = "Translate x", blurb = "The X Translation", default = None)]
|
||||
// translate_x: RefCell<Option<String>>,
|
||||
|
||||
#[property(get, set, nick = "Translate y", blurb = "The Y Translation", default = None)]
|
||||
translate_y: RefCell<Option<String>>,
|
||||
// #[property(get, set, nick = "Translate y", blurb = "The Y Translation", default = None)]
|
||||
// translate_y: RefCell<Option<String>>,
|
||||
|
||||
#[property(get, set, nick = "Scale x", blurb = "The amount to scale in x", default = None)]
|
||||
scale_x: RefCell<Option<String>>,
|
||||
// #[property(get, set, nick = "Scale x", blurb = "The amount to scale in x", default = None)]
|
||||
// scale_x: RefCell<Option<String>>,
|
||||
|
||||
#[property(get, set, nick = "Scale y", blurb = "The amount to scale in y", default = None)]
|
||||
scale_y: RefCell<Option<String>>,
|
||||
// #[property(get, set, nick = "Scale y", blurb = "The amount to scale in y", default = None)]
|
||||
// scale_y: RefCell<Option<String>>,
|
||||
|
||||
content: RefCell<Option<gtk::Widget>>,
|
||||
}
|
||||
// content: RefCell<Option<gtk4::Widget>>,
|
||||
// }
|
||||
|
||||
// This should match the default values from the ParamSpecs
|
||||
impl Default for TransformPriv {
|
||||
fn default() -> Self {
|
||||
TransformPriv {
|
||||
rotate: RefCell::new(0.0),
|
||||
transform_origin_x: RefCell::new(None),
|
||||
transform_origin_y: RefCell::new(None),
|
||||
translate_x: RefCell::new(None),
|
||||
translate_y: RefCell::new(None),
|
||||
scale_x: RefCell::new(None),
|
||||
scale_y: RefCell::new(None),
|
||||
content: RefCell::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
// // This should match the default values from the ParamSpecs
|
||||
// impl Default for TransformPriv {
|
||||
// fn default() -> Self {
|
||||
// TransformPriv {
|
||||
// rotate: RefCell::new(0.0),
|
||||
// transform_origin_x: RefCell::new(None),
|
||||
// transform_origin_y: RefCell::new(None),
|
||||
// translate_x: RefCell::new(None),
|
||||
// translate_y: RefCell::new(None),
|
||||
// scale_x: RefCell::new(None),
|
||||
// scale_y: RefCell::new(None),
|
||||
// content: RefCell::new(None),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl ObjectImpl for TransformPriv {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
Self::derived_properties()
|
||||
}
|
||||
// impl ObjectImpl for TransformPriv {
|
||||
// fn properties() -> &'static [glib::ParamSpec] {
|
||||
// Self::derived_properties()
|
||||
// }
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
match pspec.name() {
|
||||
"rotate" => {
|
||||
self.rotate.replace(value.get().unwrap());
|
||||
self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||
}
|
||||
"transform-origin-x" => {
|
||||
self.transform_origin_x.replace(value.get().unwrap());
|
||||
self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||
}
|
||||
"transform-origin-y" => {
|
||||
self.transform_origin_y.replace(value.get().unwrap());
|
||||
self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||
}
|
||||
"translate-x" => {
|
||||
self.translate_x.replace(value.get().unwrap());
|
||||
self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||
}
|
||||
"translate-y" => {
|
||||
self.translate_y.replace(value.get().unwrap());
|
||||
self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||
}
|
||||
"scale-x" => {
|
||||
self.scale_x.replace(value.get().unwrap());
|
||||
self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||
}
|
||||
"scale-y" => {
|
||||
self.scale_y.replace(value.get().unwrap());
|
||||
self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||
}
|
||||
x => panic!("Tried to set inexistant property of Transform: {}", x,),
|
||||
}
|
||||
}
|
||||
// fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
// match pspec.name() {
|
||||
// "rotate" => {
|
||||
// self.rotate.replace(value.get().unwrap());
|
||||
// self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||
// }
|
||||
// "transform-origin-x" => {
|
||||
// self.transform_origin_x.replace(value.get().unwrap());
|
||||
// self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||
// }
|
||||
// "transform-origin-y" => {
|
||||
// self.transform_origin_y.replace(value.get().unwrap());
|
||||
// self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||
// }
|
||||
// "translate-x" => {
|
||||
// self.translate_x.replace(value.get().unwrap());
|
||||
// self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||
// }
|
||||
// "translate-y" => {
|
||||
// self.translate_y.replace(value.get().unwrap());
|
||||
// self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||
// }
|
||||
// "scale-x" => {
|
||||
// self.scale_x.replace(value.get().unwrap());
|
||||
// self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||
// }
|
||||
// "scale-y" => {
|
||||
// self.scale_y.replace(value.get().unwrap());
|
||||
// self.obj().queue_draw(); // Queue a draw call with the updated value
|
||||
// }
|
||||
// x => panic!("Tried to set inexistant property of Transform: {}", x,),
|
||||
// }
|
||||
// }
|
||||
|
||||
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
self.derived_property(id, pspec)
|
||||
}
|
||||
}
|
||||
// fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
// self.derived_property(id, pspec)
|
||||
// }
|
||||
// }
|
||||
|
||||
#[object_subclass]
|
||||
impl ObjectSubclass for TransformPriv {
|
||||
type ParentType = gtk::Bin;
|
||||
type Type = Transform;
|
||||
// #[object_subclass]
|
||||
// impl ObjectSubclass for TransformPriv {
|
||||
// type ParentType = gtk4::Bin;
|
||||
// type Type = Transform;
|
||||
|
||||
const NAME: &'static str = "Transform";
|
||||
// const NAME: &'static str = "Transform";
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.set_css_name("transform");
|
||||
}
|
||||
}
|
||||
// fn class_init(klass: &mut Self::Class) {
|
||||
// klass.set_css_name("transform");
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Default for Transform {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
// impl Default for Transform {
|
||||
// fn default() -> Self {
|
||||
// Self::new()
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Transform {
|
||||
pub fn new() -> Self {
|
||||
glib::Object::new::<Self>()
|
||||
}
|
||||
}
|
||||
// impl Transform {
|
||||
// pub fn new() -> Self {
|
||||
// glib::Object::new::<Self>()
|
||||
// }
|
||||
// }
|
||||
|
||||
impl ContainerImpl for TransformPriv {
|
||||
fn add(&self, widget: >k::Widget) {
|
||||
if let Some(content) = &*self.content.borrow() {
|
||||
// TODO: Handle this error when populating children widgets instead
|
||||
error_handling_ctx::print_error(anyhow!(
|
||||
"Error, trying to add multiple children to a circular-progress widget"
|
||||
));
|
||||
self.parent_remove(content);
|
||||
}
|
||||
self.parent_add(widget);
|
||||
self.content.replace(Some(widget.clone()));
|
||||
}
|
||||
}
|
||||
// impl ContainerImpl for TransformPriv {
|
||||
// fn add(&self, widget: >k4::Widget) {
|
||||
// if let Some(content) = &*self.content.borrow() {
|
||||
// // TODO: Handle this error when populating children widgets instead
|
||||
// error_handling_ctx::print_error(anyhow!(
|
||||
// "Error, trying to add multiple children to a circular-progress widget"
|
||||
// ));
|
||||
// self.parent_remove(content);
|
||||
// }
|
||||
// self.parent_add(widget);
|
||||
// self.content.replace(Some(widget.clone()));
|
||||
// }
|
||||
// }
|
||||
|
||||
impl BinImpl for TransformPriv {}
|
||||
impl WidgetImpl for TransformPriv {
|
||||
fn draw(&self, cr: >k::cairo::Context) -> glib::Propagation {
|
||||
let res: Result<()> = (|| {
|
||||
let rotate = *self.rotate.borrow();
|
||||
let total_width = self.obj().allocated_width() as f64;
|
||||
let total_height = self.obj().allocated_height() as f64;
|
||||
// impl BinImpl for TransformPriv {}
|
||||
// impl WidgetImpl for TransformPriv {
|
||||
// fn draw(&self, cr: >k4::cairo::Context) -> glib::Propagation {
|
||||
// let res: Result<()> = (|| {
|
||||
// let rotate = *self.rotate.borrow();
|
||||
// let total_width = self.obj().allocated_width() as f64;
|
||||
// let total_height = self.obj().allocated_height() as f64;
|
||||
|
||||
cr.save()?;
|
||||
// cr.save()?;
|
||||
|
||||
let transform_origin_x = match &*self.transform_origin_x.borrow() {
|
||||
Some(rcx) => {
|
||||
NumWithUnit::from_str(rcx)?.pixels_relative_to(total_width as i32) as f64
|
||||
}
|
||||
None => 0.0,
|
||||
};
|
||||
let transform_origin_y = match &*self.transform_origin_y.borrow() {
|
||||
Some(rcy) => {
|
||||
NumWithUnit::from_str(rcy)?.pixels_relative_to(total_height as i32) as f64
|
||||
}
|
||||
None => 0.0,
|
||||
};
|
||||
// let transform_origin_x = match &*self.transform_origin_x.borrow() {
|
||||
// Some(rcx) => {
|
||||
// NumWithUnit::from_str(rcx)?.pixels_relative_to(total_width as i32) as f64
|
||||
// }
|
||||
// None => 0.0,
|
||||
// };
|
||||
// let transform_origin_y = match &*self.transform_origin_y.borrow() {
|
||||
// Some(rcy) => {
|
||||
// NumWithUnit::from_str(rcy)?.pixels_relative_to(total_height as i32) as f64
|
||||
// }
|
||||
// None => 0.0,
|
||||
// };
|
||||
|
||||
let translate_x = match &*self.translate_x.borrow() {
|
||||
Some(tx) => {
|
||||
NumWithUnit::from_str(tx)?.pixels_relative_to(total_width as i32) as f64
|
||||
}
|
||||
None => 0.0,
|
||||
};
|
||||
// let translate_x = match &*self.translate_x.borrow() {
|
||||
// Some(tx) => {
|
||||
// NumWithUnit::from_str(tx)?.pixels_relative_to(total_width as i32) as f64
|
||||
// }
|
||||
// None => 0.0,
|
||||
// };
|
||||
|
||||
let translate_y = match &*self.translate_y.borrow() {
|
||||
Some(ty) => {
|
||||
NumWithUnit::from_str(ty)?.pixels_relative_to(total_height as i32) as f64
|
||||
}
|
||||
None => 0.0,
|
||||
};
|
||||
// let translate_y = match &*self.translate_y.borrow() {
|
||||
// Some(ty) => {
|
||||
// NumWithUnit::from_str(ty)?.pixels_relative_to(total_height as i32) as f64
|
||||
// }
|
||||
// None => 0.0,
|
||||
// };
|
||||
|
||||
let scale_x = match &*self.scale_x.borrow() {
|
||||
Some(sx) => {
|
||||
NumWithUnit::from_str(sx)?.perc_relative_to(total_width as i32) as f64 / 100.0
|
||||
}
|
||||
None => 1.0,
|
||||
};
|
||||
// let scale_x = match &*self.scale_x.borrow() {
|
||||
// Some(sx) => {
|
||||
// NumWithUnit::from_str(sx)?.perc_relative_to(total_width as i32) as f64 / 100.0
|
||||
// }
|
||||
// None => 1.0,
|
||||
// };
|
||||
|
||||
let scale_y = match &*self.scale_y.borrow() {
|
||||
Some(sy) => {
|
||||
NumWithUnit::from_str(sy)?.perc_relative_to(total_height as i32) as f64 / 100.0
|
||||
}
|
||||
None => 1.0,
|
||||
};
|
||||
// let scale_y = match &*self.scale_y.borrow() {
|
||||
// Some(sy) => {
|
||||
// NumWithUnit::from_str(sy)?.perc_relative_to(total_height as i32) as f64 / 100.0
|
||||
// }
|
||||
// None => 1.0,
|
||||
// };
|
||||
|
||||
cr.translate(transform_origin_x, transform_origin_y);
|
||||
cr.rotate(perc_to_rad(rotate));
|
||||
cr.translate(translate_x - transform_origin_x, translate_y - transform_origin_y);
|
||||
cr.scale(scale_x, scale_y);
|
||||
// cr.translate(transform_origin_x, transform_origin_y);
|
||||
// cr.rotate(perc_to_rad(rotate));
|
||||
// cr.translate(translate_x - transform_origin_x, translate_y - transform_origin_y);
|
||||
// cr.scale(scale_x, scale_y);
|
||||
|
||||
// Children widget
|
||||
if let Some(child) = &*self.content.borrow() {
|
||||
self.obj().propagate_draw(child, cr);
|
||||
}
|
||||
// // Children widget
|
||||
// if let Some(child) = &*self.content.borrow() {
|
||||
// self.obj().propagate_draw(child, cr);
|
||||
// }
|
||||
|
||||
cr.restore()?;
|
||||
Ok(())
|
||||
})();
|
||||
// cr.restore()?;
|
||||
// Ok(())
|
||||
// })();
|
||||
|
||||
if let Err(error) = res {
|
||||
error_handling_ctx::print_error(error)
|
||||
};
|
||||
// if let Err(error) = res {
|
||||
// error_handling_ctx::print_error(error)
|
||||
// };
|
||||
|
||||
glib::Propagation::Proceed
|
||||
}
|
||||
}
|
||||
// glib::Propagation::Proceed
|
||||
// }
|
||||
// }
|
||||
|
||||
fn perc_to_rad(n: f64) -> f64 {
|
||||
(n / 100f64) * 2f64 * std::f64::consts::PI
|
||||
}
|
||||
// fn perc_to_rad(n: f64) -> f64 {
|
||||
// (n / 100f64) * 2f64 * std::f64::consts::PI
|
||||
// }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,22 @@
|
||||
use crate::gtk::prelude::LabelExt;
|
||||
use anyhow::{anyhow, Result};
|
||||
use gtk::pango;
|
||||
use gtk4::glib;
|
||||
use gtk4::glib::gobject_ffi;
|
||||
use gtk4::glib::translate::{FromGlibPtrNone, IntoGlib};
|
||||
use gtk4::glib::Value;
|
||||
use gtk4::pango;
|
||||
use gtk4::prelude::{Cast, ObjectExt, RangeExt, StaticType, ToValue};
|
||||
use rhai::Map;
|
||||
use std::process::Command;
|
||||
|
||||
// Run a command and get the output
|
||||
pub(super) fn run_command<T>(
|
||||
timeout: std::time::Duration,
|
||||
cmd: &str,
|
||||
args: &[T],
|
||||
injected_vars: Option<Vec<(String, String)>>,
|
||||
) where
|
||||
pub(super) fn run_command<T>(timeout: std::time::Duration, cmd: &str, args: &[T])
|
||||
where
|
||||
T: 'static + std::fmt::Display + Send + Sync + Clone,
|
||||
{
|
||||
if cmd.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
use wait_timeout::ChildExt;
|
||||
let cmd = replace_placeholders(cmd, args);
|
||||
std::thread::Builder::new()
|
||||
@@ -26,12 +30,6 @@ pub(super) fn run_command<T>(
|
||||
let mut command = Command::new("/bin/sh");
|
||||
command.arg("-c").arg(&cmd);
|
||||
|
||||
if let Some(vars) = injected_vars {
|
||||
for (key, value) in vars {
|
||||
command.env(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
let child = command.spawn();
|
||||
match child {
|
||||
Ok(mut child) => match child.wait_timeout(timeout) {
|
||||
@@ -85,29 +83,29 @@ pub fn dynamic_eq(a: &rhai::Dynamic, b: &rhai::Dynamic) -> bool {
|
||||
}
|
||||
|
||||
/// ALL WIDGETS
|
||||
pub(super) fn parse_align(o: &str) -> Result<gtk::Align> {
|
||||
pub(super) fn parse_align(o: &str) -> Result<gtk4::Align> {
|
||||
match o.to_ascii_lowercase().as_str() {
|
||||
"fill" => Ok(gtk::Align::Fill),
|
||||
"baseline" => Ok(gtk::Align::Baseline),
|
||||
"center" => Ok(gtk::Align::Center),
|
||||
"start" => Ok(gtk::Align::Start),
|
||||
"end" => Ok(gtk::Align::End),
|
||||
"fill" => Ok(gtk4::Align::Fill),
|
||||
"baseline" => Ok(gtk4::Align::Baseline),
|
||||
"center" => Ok(gtk4::Align::Center),
|
||||
"start" => Ok(gtk4::Align::Start),
|
||||
"end" => Ok(gtk4::Align::End),
|
||||
other => Err(anyhow!("Invalid alignment: {}", other)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gtk Box
|
||||
pub(super) fn parse_orientation(ori: &str) -> Result<gtk::Orientation> {
|
||||
pub(super) fn parse_orientation(ori: &str) -> Result<gtk4::Orientation> {
|
||||
match ori.to_ascii_lowercase().as_str() {
|
||||
"h" | "horizontal" => Ok(gtk::Orientation::Horizontal),
|
||||
"v" | "vertical" => Ok(gtk::Orientation::Vertical),
|
||||
"h" | "horizontal" => Ok(gtk4::Orientation::Horizontal),
|
||||
"v" | "vertical" => Ok(gtk4::Orientation::Vertical),
|
||||
other => Err(anyhow!("Invalid orientation: {}", other)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gtk Label
|
||||
pub(super) fn apply_ellipsize_settings(
|
||||
label: >k::Label,
|
||||
label: >k4::Label,
|
||||
truncate: bool,
|
||||
limit_width: i32,
|
||||
truncate_left: bool,
|
||||
@@ -136,12 +134,12 @@ pub(super) fn parse_gravity(s: &str) -> Result<pango::Gravity> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn parse_justification(s: &str) -> Result<gtk::Justification> {
|
||||
pub(super) fn parse_justification(s: &str) -> Result<gtk4::Justification> {
|
||||
match s.to_ascii_lowercase().as_str() {
|
||||
"left" => Ok(gtk::Justification::Left),
|
||||
"right" => Ok(gtk::Justification::Right),
|
||||
"center" => Ok(gtk::Justification::Center),
|
||||
"fill" => Ok(gtk::Justification::Fill),
|
||||
"left" => Ok(gtk4::Justification::Left),
|
||||
"right" => Ok(gtk4::Justification::Right),
|
||||
"center" => Ok(gtk4::Justification::Center),
|
||||
"fill" => Ok(gtk4::Justification::Fill),
|
||||
_ => Err(anyhow!("Invalid justification: '{}'", s)),
|
||||
}
|
||||
}
|
||||
@@ -156,12 +154,23 @@ pub(super) fn parse_wrap_mode(s: &str) -> Result<pango::WrapMode> {
|
||||
}
|
||||
|
||||
/// Gtk scale (slider)
|
||||
pub(super) fn parse_position_type(s: &str) -> Result<gtk::PositionType> {
|
||||
pub(super) fn parse_position_type(s: &str) -> Result<gtk4::PositionType> {
|
||||
match s.to_ascii_lowercase().as_str() {
|
||||
"left" => Ok(gtk::PositionType::Left),
|
||||
"right" => Ok(gtk::PositionType::Right),
|
||||
"top" => Ok(gtk::PositionType::Top),
|
||||
"bottom" => Ok(gtk::PositionType::Bottom),
|
||||
"left" => Ok(gtk4::PositionType::Left),
|
||||
"right" => Ok(gtk4::PositionType::Right),
|
||||
"top" => Ok(gtk4::PositionType::Top),
|
||||
"bottom" => Ok(gtk4::PositionType::Bottom),
|
||||
_ => Err(anyhow!("Invalid position type: '{}'", s)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gtk flow box
|
||||
pub(super) fn parse_selection_model(s: &str) -> Result<gtk4::SelectionMode> {
|
||||
match s.to_ascii_lowercase().as_str() {
|
||||
"none" => Ok(gtk4::SelectionMode::None),
|
||||
"single" => Ok(gtk4::SelectionMode::Single),
|
||||
"browse" => Ok(gtk4::SelectionMode::Browse),
|
||||
"multiple" => Ok(gtk4::SelectionMode::Multiple),
|
||||
_ => Err(anyhow!("Invalid position type: '{}'", s)),
|
||||
}
|
||||
}
|
||||
@@ -182,32 +191,18 @@ where
|
||||
}
|
||||
|
||||
/// Revealer
|
||||
pub(super) fn parse_revealer_transition(t: &str) -> Result<gtk::RevealerTransitionType> {
|
||||
pub(super) fn parse_revealer_transition(t: &str) -> Result<gtk4::RevealerTransitionType> {
|
||||
match t.to_ascii_lowercase().as_str() {
|
||||
"slideright" => Ok(gtk::RevealerTransitionType::SlideRight),
|
||||
"slideleft" => Ok(gtk::RevealerTransitionType::SlideLeft),
|
||||
"slideup" => Ok(gtk::RevealerTransitionType::SlideUp),
|
||||
"slidedown" => Ok(gtk::RevealerTransitionType::SlideDown),
|
||||
"fade" | "crossfade" => Ok(gtk::RevealerTransitionType::Crossfade),
|
||||
"none" => Ok(gtk::RevealerTransitionType::None),
|
||||
"slideright" => Ok(gtk4::RevealerTransitionType::SlideRight),
|
||||
"slideleft" => Ok(gtk4::RevealerTransitionType::SlideLeft),
|
||||
"slideup" => Ok(gtk4::RevealerTransitionType::SlideUp),
|
||||
"slidedown" => Ok(gtk4::RevealerTransitionType::SlideDown),
|
||||
"fade" | "crossfade" => Ok(gtk4::RevealerTransitionType::Crossfade),
|
||||
"none" => Ok(gtk4::RevealerTransitionType::None),
|
||||
_ => Err(anyhow!("Invalid transition: '{}'", t)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gtk Image
|
||||
// icon-size - "menu", "small-toolbar", "toolbar", "large-toolbar", "button", "dnd", "dialog"
|
||||
pub(super) fn parse_icon_size(o: &str) -> Result<gtk::IconSize> {
|
||||
match o.to_ascii_lowercase().as_str() {
|
||||
"menu" => Ok(gtk::IconSize::Menu),
|
||||
"small-toolbar" | "toolbar" => Ok(gtk::IconSize::SmallToolbar),
|
||||
"large-toolbar" => Ok(gtk::IconSize::LargeToolbar),
|
||||
"button" => Ok(gtk::IconSize::Button),
|
||||
"dnd" => Ok(gtk::IconSize::Dnd),
|
||||
"dialog" => Ok(gtk::IconSize::Dialog),
|
||||
_ => Err(anyhow!("Invalid icon size: '{}'", o)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Event box
|
||||
// dragtype - "file", "text"
|
||||
pub(super) enum DragEntryType {
|
||||
@@ -225,14 +220,116 @@ pub(super) fn parse_dragtype(o: &str) -> Result<DragEntryType> {
|
||||
|
||||
/// Stack widget
|
||||
// transition - "slideright", "slideleft", "slideup", "slidedown", "crossfade", "none"
|
||||
pub(super) fn parse_stack_transition(t: &str) -> Result<gtk::StackTransitionType> {
|
||||
pub(super) fn parse_stack_transition(t: &str) -> Result<gtk4::StackTransitionType> {
|
||||
match t.to_ascii_lowercase().as_str() {
|
||||
"slideright" => Ok(gtk::StackTransitionType::SlideRight),
|
||||
"slideleft" => Ok(gtk::StackTransitionType::SlideLeft),
|
||||
"slideup" => Ok(gtk::StackTransitionType::SlideUp),
|
||||
"slidedown" => Ok(gtk::StackTransitionType::SlideDown),
|
||||
"fade" | "crossfade" => Ok(gtk::StackTransitionType::Crossfade),
|
||||
"none" => Ok(gtk::StackTransitionType::None),
|
||||
"slideright" => Ok(gtk4::StackTransitionType::SlideRight),
|
||||
"slideleft" => Ok(gtk4::StackTransitionType::SlideLeft),
|
||||
"slideup" => Ok(gtk4::StackTransitionType::SlideUp),
|
||||
"slidedown" => Ok(gtk4::StackTransitionType::SlideDown),
|
||||
"fade" | "crossfade" => Ok(gtk4::StackTransitionType::Crossfade),
|
||||
"none" => Ok(gtk4::StackTransitionType::None),
|
||||
_ => Err(anyhow!("Invalid stack transition: '{}'", t)),
|
||||
}
|
||||
}
|
||||
|
||||
// For localbind
|
||||
pub(super) fn set_property_from_string_anywhere(
|
||||
widget: >k4::Widget,
|
||||
prop_name: &str,
|
||||
value_str: &str,
|
||||
) {
|
||||
fn convert(pspec: &glib::ParamSpec, value_str: &str) -> Option<Value> {
|
||||
let value_type = pspec.value_type();
|
||||
|
||||
if value_type == f64::static_type() {
|
||||
value_str.parse::<f64>().ok().map(|v| v.to_value())
|
||||
} else if value_type == i32::static_type() {
|
||||
value_str.parse::<i32>().ok().map(|v| v.to_value())
|
||||
} else if value_type == bool::static_type() {
|
||||
value_str.parse::<bool>().ok().map(|v| v.to_value())
|
||||
} else if value_type == String::static_type() {
|
||||
Some(value_str.to_value())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
let obj: &glib::Object = widget.upcast_ref();
|
||||
|
||||
if let Some(pspec) = obj.find_property(prop_name) {
|
||||
if let Some(gv) = convert(&pspec, value_str) {
|
||||
obj.set_property(prop_name, &gv);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
for iface_type in obj.type_().interfaces() {
|
||||
for pspec in list_interface_properties(iface_type) {
|
||||
if pspec.name() == prop_name {
|
||||
if let Some(v) = convert(&pspec, value_str) {
|
||||
obj.set_property(prop_name, &v);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(range) = widget.downcast_ref::<gtk4::Range>() {
|
||||
let range_obj: &glib::Object = range.upcast_ref();
|
||||
|
||||
if let Some(pspec) = range_obj.find_property(prop_name) {
|
||||
if let Some(gv) = convert(&pspec, value_str) {
|
||||
range_obj.set_property(prop_name, &gv);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let adj = range.adjustment();
|
||||
let adj_obj: &glib::Object = adj.upcast_ref();
|
||||
|
||||
if let Some(pspec) = adj_obj.find_property(prop_name) {
|
||||
if let Some(gv) = convert(&pspec, value_str) {
|
||||
adj_obj.set_property(prop_name, &gv);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log::error!("Property '{}' not found on widget {}", prop_name, obj.type_().name());
|
||||
}
|
||||
|
||||
unsafe fn list_interface_properties(iface_type: glib::Type) -> Vec<glib::ParamSpec> {
|
||||
let mut n_props = 0;
|
||||
|
||||
let iface_ptr = gobject_ffi::g_type_default_interface_ref(iface_type.into_glib());
|
||||
if iface_ptr.is_null() {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let props_ptr =
|
||||
gobject_ffi::g_object_interface_list_properties(iface_ptr as *mut _, &mut n_props);
|
||||
|
||||
let props = (0..n_props)
|
||||
.map(|i| {
|
||||
let p = *props_ptr.add(i as usize);
|
||||
glib::ParamSpec::from_glib_none(p)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
gobject_ffi::g_type_default_interface_unref(iface_ptr);
|
||||
|
||||
props
|
||||
}
|
||||
|
||||
/// Picture widget
|
||||
pub(super) fn parse_content_fit(cf: &str) -> Result<gtk4::ContentFit> {
|
||||
match cf.to_ascii_lowercase().as_str() {
|
||||
"fill" => Ok(gtk4::ContentFit::Fill),
|
||||
"contain" => Ok(gtk4::ContentFit::Contain),
|
||||
"cover" => Ok(gtk4::ContentFit::Cover),
|
||||
"scaledown" => Ok(gtk4::ContentFit::ScaleDown),
|
||||
_ => Err(anyhow!("Invalid content fit: '{}'", cf)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
use gtk::glib::{self, object_subclass, wrapper, Properties};
|
||||
use gtk::{prelude::*, subclass::prelude::*};
|
||||
use std::cell::RefCell;
|
||||
|
||||
wrapper! {
|
||||
pub struct Window(ObjectSubclass<WindowPriv>)
|
||||
@extends gtk::Window, gtk::Bin, gtk::Container, gtk::Widget, @implements gtk::Buildable;
|
||||
}
|
||||
|
||||
#[derive(Properties)]
|
||||
#[properties(wrapper_type = Window)]
|
||||
pub struct WindowPriv {
|
||||
#[property(get, name = "x", nick = "X", blurb = "Global x coordinate", default = 0)]
|
||||
x: RefCell<i32>,
|
||||
|
||||
#[property(get, name = "y", nick = "Y", blurb = "Global y coordinate", default = 0)]
|
||||
y: RefCell<i32>,
|
||||
}
|
||||
|
||||
// This should match the default values from the ParamSpecs
|
||||
impl Default for WindowPriv {
|
||||
fn default() -> Self {
|
||||
WindowPriv { x: RefCell::new(0), y: RefCell::new(0) }
|
||||
}
|
||||
}
|
||||
|
||||
#[object_subclass]
|
||||
impl ObjectSubclass for WindowPriv {
|
||||
type ParentType = gtk::Window;
|
||||
type Type = Window;
|
||||
|
||||
const NAME: &'static str = "WindowEwwii";
|
||||
}
|
||||
|
||||
impl Default for Window {
|
||||
fn default() -> Self {
|
||||
glib::Object::new::<Self>()
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(type_: gtk::WindowType, x_: i32, y_: i32) -> Self {
|
||||
let w: Self = glib::Object::builder().property("type", type_).build();
|
||||
let priv_ = w.imp();
|
||||
priv_.x.replace(x_);
|
||||
priv_.y.replace(y_);
|
||||
w
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for WindowPriv {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
Self::derived_properties()
|
||||
}
|
||||
|
||||
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
self.derived_property(id, pspec)
|
||||
}
|
||||
}
|
||||
impl WindowImpl for WindowPriv {}
|
||||
impl BinImpl for WindowPriv {}
|
||||
impl ContainerImpl for WindowPriv {}
|
||||
impl WidgetImpl for WindowPriv {}
|
||||
@@ -81,6 +81,7 @@ impl BackendWindowOptionsDef {
|
||||
exclusive: Self::get_optional(map, "exclusive")?,
|
||||
focusable,
|
||||
namespace: Self::get_optional(map, "namespace")?,
|
||||
force_normal: Self::get_optional(map, "force_normal")?,
|
||||
};
|
||||
|
||||
Ok(Self { wayland, x11 })
|
||||
@@ -167,6 +168,7 @@ pub struct WlBackendWindowOptions {
|
||||
pub exclusive: bool,
|
||||
pub focusable: WlWindowFocusable,
|
||||
pub namespace: Option<String>,
|
||||
pub force_normal: bool,
|
||||
}
|
||||
|
||||
/// Unevaluated form of [`WlBackendWindowOptions`]
|
||||
@@ -175,6 +177,7 @@ pub struct WlBackendWindowOptionsDef {
|
||||
pub exclusive: Option<bool>,
|
||||
pub focusable: Option<String>,
|
||||
pub namespace: Option<String>,
|
||||
pub force_normal: Option<bool>,
|
||||
}
|
||||
|
||||
impl WlBackendWindowOptionsDef {
|
||||
@@ -189,6 +192,10 @@ impl WlBackendWindowOptionsDef {
|
||||
None => WlWindowFocusable::default(),
|
||||
},
|
||||
namespace: properties.get("namespace").map(|d| d.clone_cast::<String>()),
|
||||
force_normal: properties
|
||||
.get("force_normal")
|
||||
.map(|d| d.clone_cast::<bool>())
|
||||
.unwrap_or(false),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -219,9 +226,6 @@ impl FromStr for WlWindowFocusable {
|
||||
"none" => Self::None,
|
||||
"exclusive" => Self::Exclusive,
|
||||
"ondemand" => Self::OnDemand,
|
||||
// legacy support
|
||||
"true" => Self::Exclusive,
|
||||
"false" => Self::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,12 +33,13 @@ impl NumWithUnit {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn perc_relative_to(&self, max: i32) -> f32 {
|
||||
match *self {
|
||||
NumWithUnit::Percent(n) => n,
|
||||
NumWithUnit::Pixels(n) => ((n as f64 / max as f64) * 100.0) as f32,
|
||||
}
|
||||
}
|
||||
// add back when needed
|
||||
// pub fn perc_relative_to(&self, max: i32) -> f32 {
|
||||
// match *self {
|
||||
// NumWithUnit::Percent(n) => n,
|
||||
// NumWithUnit::Pixels(n) => ((n as f64 / max as f64) * 100.0) as f32,
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
impl FromStr for NumWithUnit {
|
||||
|
||||
23
crates/ewwii_plugin_api/Cargo.toml
Normal file
23
crates/ewwii_plugin_api/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "ewwii_plugin_api"
|
||||
version = "0.7.0"
|
||||
authors = ["byson94 <byson94wastaken@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
description = "A shared library for building plugins for ewwii"
|
||||
repository = "https://github.com/byson94/ewwii"
|
||||
homepage = "https://github.com/byson94/ewwii"
|
||||
|
||||
[features]
|
||||
default = ["include-gtk4", "include-rhai"]
|
||||
|
||||
## Include the gtk4 dependency
|
||||
include-gtk4 = ["dep:gtk4"]
|
||||
|
||||
## Include the rhai dependency
|
||||
include-rhai = ["dep:rhai"]
|
||||
|
||||
[dependencies]
|
||||
# rhai crate features should exactly match that of rhai_impl
|
||||
rhai = { workspace = true, optional = true, features = ["internals"] }
|
||||
gtk4 = { workspace = true, optional = true }
|
||||
28
crates/ewwii_plugin_api/README.md
Normal file
28
crates/ewwii_plugin_api/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# ewwii_plugin_api
|
||||
|
||||
A shared interface providing traits for building plugins for ewwii.
|
||||
|
||||
## Example
|
||||
|
||||
A simple example showing how to use this interface is provided below:
|
||||
|
||||
```rust
|
||||
use ewwii_plugin_api::{EwwiiAPI, Plugin, export_plugin};
|
||||
|
||||
pub struct DummyStructure;
|
||||
|
||||
impl Plugin for DummyStructure {
|
||||
// critical for ewwii to launch the plugin
|
||||
fn init(&self, host: &dyn EwwiiAPI) {
|
||||
// will be printed by the host
|
||||
host.log("Plugin says Hello!");
|
||||
host.rhai_engine_action(Box::new(|engine| {
|
||||
let ast = engine.compile("1+1");
|
||||
println!("Compiled AST: {:#?}", ast);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Critical for ewwii to see the plugin
|
||||
export_plugin!(DummyStructure);
|
||||
```
|
||||
11
crates/ewwii_plugin_api/src/example.rs
Normal file
11
crates/ewwii_plugin_api/src/example.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
//! A module providing example implementations of plugins
|
||||
|
||||
/// An example plugin that can be exported directly
|
||||
pub struct ExamplePlugin;
|
||||
|
||||
impl crate::Plugin for ExamplePlugin {
|
||||
/// Example code that initalizes the plugin
|
||||
fn init(&self, host: &dyn crate::EwwiiAPI) {
|
||||
host.log("Example plugin says Hello!");
|
||||
}
|
||||
}
|
||||
63
crates/ewwii_plugin_api/src/export_macros.rs
Normal file
63
crates/ewwii_plugin_api/src/export_macros.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
//! Module implementing macros
|
||||
|
||||
/// Macro to implement and export a plugin in a single step.
|
||||
/// With this macro, users can write their plugin code directly
|
||||
/// without having to manually implement each trait.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// The following example shows how you can use this macro to
|
||||
/// easily make plugins in a single step.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use ewwii_plugin_api::auto_plugin;
|
||||
///
|
||||
/// auto_plugin!(MyPluginName, {
|
||||
/// // host variable is passed in automatically
|
||||
/// host.log("Easy, huh?");
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// ## When not to use it
|
||||
///
|
||||
/// This macro shall not be used if you prefer flexibility and safety.
|
||||
/// The manual approach is verbose, but is way safer and flexible than using this macro.
|
||||
#[macro_export]
|
||||
macro_rules! auto_plugin {
|
||||
($struct_name:ident, $init_block:block) => {
|
||||
pub struct $struct_name;
|
||||
|
||||
// Implement the Plugin trait
|
||||
impl $crate::Plugin for $struct_name {
|
||||
fn init(&self, host: &dyn $crate::EwwiiAPI) {
|
||||
$init_block
|
||||
}
|
||||
}
|
||||
|
||||
$crate::export_plugin!($struct_name);
|
||||
};
|
||||
}
|
||||
|
||||
/// Automatically implements `create_plugin` for a given fieldless structure
|
||||
#[macro_export]
|
||||
macro_rules! export_plugin {
|
||||
($plugin_struct:path) => {
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn create_plugin() -> Box<dyn $crate::Plugin> {
|
||||
Box::new($plugin_struct)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Automatically implements `create_plugin` for a given structure that has fields.
|
||||
///
|
||||
/// This macro expects the structure to have fields and also implement a `default()` method.
|
||||
#[macro_export]
|
||||
macro_rules! export_stateful_plugin {
|
||||
($plugin_struct:path) => {
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn create_plugin() -> Box<dyn $crate::Plugin> {
|
||||
Box::new(<$plugin_struct>::default())
|
||||
}
|
||||
};
|
||||
}
|
||||
180
crates/ewwii_plugin_api/src/lib.rs
Normal file
180
crates/ewwii_plugin_api/src/lib.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
//! # ewwii_plugin_api - A plugin interface for ewwii
|
||||
//!
|
||||
//! `ewwii_plguin_api` is a shared list of traits
|
||||
//! that both ewwii and its plugins can use.
|
||||
//! This crate simplifies and provides a safe way for building
|
||||
//! plugins for ewwii.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! The following example shows how this crate shall be used to build ewwii plugins:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use ewwii_plugin_api::{EwwiiAPI, Plugin, export_plugin};
|
||||
//!
|
||||
//! pub struct DummyStructure;
|
||||
//!
|
||||
//! impl Plugin for DummyStructure {
|
||||
//! // critical for ewwii to launch the plugin
|
||||
//! fn init(&self, host: &dyn EwwiiAPI) {
|
||||
//! // will be printed by the host
|
||||
//! host.log("Plugin says Hello!");
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! // Critical for ewwii to load the plugin
|
||||
//! export_plugin!(DummyStructure);
|
||||
//! ```
|
||||
|
||||
mod export_macros;
|
||||
|
||||
pub mod example;
|
||||
pub mod rhai_backend;
|
||||
pub mod widget_backend;
|
||||
|
||||
#[cfg(feature = "include-rhai")]
|
||||
pub use rhai;
|
||||
|
||||
#[cfg(feature = "include-gtk4")]
|
||||
pub use gtk4;
|
||||
|
||||
/// The shared trait defining the Ewwii plugin API
|
||||
pub trait EwwiiAPI: Send + Sync {
|
||||
// == General Stuff == //
|
||||
/// Print a message from the host
|
||||
fn print(&self, msg: &str);
|
||||
/// Log a message from the host
|
||||
fn log(&self, msg: &str);
|
||||
/// Log a warning from the host
|
||||
fn warn(&self, msg: &str);
|
||||
/// Log an error from the host
|
||||
fn error(&self, msg: &str);
|
||||
|
||||
// == Rhai Manipulation Stuff == //
|
||||
/// _(include-rhai)_ Perform actions on the latest rhai engine.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ewwii_plugin_api::{EwwiiAPI, Plugin};
|
||||
///
|
||||
/// pub struct DummyStructure;
|
||||
///
|
||||
/// impl Plugin for DummyStructure {
|
||||
/// fn init(&self, host: &dyn EwwiiAPI) {
|
||||
/// host.rhai_engine_action(Box::new(|eng| {
|
||||
/// // eng = rhai::Engine
|
||||
/// eng.set_max_expr_depths(128, 128);
|
||||
/// }));
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(feature = "include-rhai")]
|
||||
fn rhai_engine_action(
|
||||
&self,
|
||||
f: Box<dyn FnOnce(&mut rhai::Engine) + Send>,
|
||||
) -> Result<(), String>;
|
||||
|
||||
/// _(include-rhai)_ Expose a function that rhai configuration can call.
|
||||
///
|
||||
/// **NOTE:***
|
||||
///
|
||||
/// Due to TypeID mismatches, methods like `register_type`, `register_fn`,
|
||||
/// etc. won't work on the engine and may cause a crash. It is recommended
|
||||
/// to use the `register_function` API to register a funtion which `api::slib`
|
||||
/// can call to in rhai.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ewwii_plugin_api::{EwwiiAPI, Plugin, rhai_backend::RhaiFnNamespace};
|
||||
/// use rhai::Dynamic;
|
||||
///
|
||||
/// pub struct DummyStructure;
|
||||
///
|
||||
/// impl Plugin for DummyStructure {
|
||||
/// fn init(&self, host: &dyn EwwiiAPI) {
|
||||
/// host.register_function(
|
||||
/// "my_func".to_string(),
|
||||
/// RhaiFnNamespace::Global,
|
||||
/// Box::new(|args| {
|
||||
/// // Do stuff
|
||||
/// // - Perform things on the args (if needed)
|
||||
/// // - And return a value
|
||||
///
|
||||
/// Ok(Dynamic::default()) // return empty
|
||||
/// }));
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This example will register a function with signature "my_func(Array)" in rhai.
|
||||
///
|
||||
/// ## Example use in rhai
|
||||
///
|
||||
/// ```js
|
||||
/// print(my_func(["param1", "param2"]));
|
||||
/// ```
|
||||
#[cfg(feature = "include-rhai")]
|
||||
fn register_function(
|
||||
&self,
|
||||
name: String,
|
||||
namespace: rhai_backend::RhaiFnNamespace,
|
||||
f: Box<
|
||||
dyn Fn(rhai::Array) -> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> + Send + Sync,
|
||||
>,
|
||||
) -> Result<(), String>;
|
||||
|
||||
// == Widget Rendering & Logic == //
|
||||
/// Get the list of all widget id's
|
||||
fn list_widget_ids(&self) -> Result<Vec<u64>, String>;
|
||||
|
||||
/// _(include-gtk4)_ Perform actions on the latest widget registry.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ewwii_plugin_api::{EwwiiAPI, Plugin};
|
||||
///
|
||||
/// pub struct DummyStructure;
|
||||
///
|
||||
/// impl Plugin for DummyStructure {
|
||||
/// fn init(&self, host: &dyn EwwiiAPI) {
|
||||
/// host.widget_reg_action(Box::new(|wrg| {
|
||||
/// // wrg = widget_backend::WidgetRegistryRepr
|
||||
/// // The gtk4::Widget can be modified here.
|
||||
/// }));
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(feature = "include-gtk4")]
|
||||
fn widget_reg_action(
|
||||
&self,
|
||||
f: Box<dyn FnOnce(&mut widget_backend::WidgetRegistryRepr) + Send>,
|
||||
) -> Result<(), String>;
|
||||
}
|
||||
|
||||
/// The API format that the plugin should follow.
|
||||
/// This trait should be implemented for a structure and
|
||||
/// that structure should be exported via FFI.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ewwii_plugin_api::{Plugin, EwwiiAPI, export_plugin};
|
||||
///
|
||||
/// struct MyStruct;
|
||||
///
|
||||
/// impl Plugin for MyStruct {
|
||||
/// fn init(&self, host: &dyn EwwiiAPI) {
|
||||
/// /* Implementation Skipped */
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Automatically does all the FFI related exports
|
||||
/// export_plugin!(MyStruct);
|
||||
/// ```
|
||||
pub trait Plugin: Send + Sync {
|
||||
/// Function ran by host to startup plugin (and its a must-have for plugin loading)
|
||||
fn init(&self, host: &dyn EwwiiAPI);
|
||||
}
|
||||
14
crates/ewwii_plugin_api/src/rhai_backend.rs
Normal file
14
crates/ewwii_plugin_api/src/rhai_backend.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
//! Module exposing extra utilities for rhai.
|
||||
|
||||
#[cfg(feature = "include-rhai")]
|
||||
mod rhai_included {
|
||||
/// _(include-rhai)_ An enumrate providing options for
|
||||
/// function registaration namespaces.
|
||||
pub enum RhaiFnNamespace {
|
||||
Custom(String),
|
||||
Global,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "include-rhai")]
|
||||
pub use rhai_included::*;
|
||||
21
crates/ewwii_plugin_api/src/widget_backend.rs
Normal file
21
crates/ewwii_plugin_api/src/widget_backend.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
//! Module exposing structures and types from the
|
||||
//! Widget rendering and definition backend in ewwii.
|
||||
|
||||
#[cfg(feature = "include-gtk4")]
|
||||
mod gtk4_included {
|
||||
use gtk4::Widget as GtkWidget;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// _(include-gtk4)_ A representation of widget registry which holds all the
|
||||
/// information needed for the dynamic runtime engine in ewwii.
|
||||
///
|
||||
/// Not every change in this structure will be represented in the
|
||||
/// original WidgetRegistry in ewwii. Only the change on gtk4::Widget
|
||||
/// is reflected back.
|
||||
pub struct WidgetRegistryRepr<'a> {
|
||||
pub widgets: HashMap<u64, &'a mut GtkWidget>,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "include-gtk4")]
|
||||
pub use gtk4_included::*;
|
||||
@@ -10,10 +10,10 @@ homepage = "https://github.com/byson94/ewwii"
|
||||
|
||||
[dependencies]
|
||||
shared_utils.workspace = true
|
||||
scan_prop_proc.workspace = true
|
||||
|
||||
rhai = { workspace = true, features = ["internals"] }
|
||||
anyhow.workspace = true
|
||||
gtk.workspace = true
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
log.workspace = true
|
||||
once_cell.workspace = true
|
||||
@@ -22,5 +22,7 @@ ahash.workspace = true
|
||||
nix = { workspace = true, features = ["process", "fs", "signal"] }
|
||||
libc.workspace = true
|
||||
# error handling
|
||||
rhai_trace = "0.2.0"
|
||||
codespan-reporting.workspace = true
|
||||
rhai_trace = "0.3.1"
|
||||
codespan-reporting.workspace = true
|
||||
regex.workspace = true
|
||||
gtk4.workspace = true
|
||||
@@ -1,20 +1,22 @@
|
||||
use ahash::AHasher;
|
||||
use anyhow::Result;
|
||||
use rhai::Map;
|
||||
use scan_prop_proc::scan_prop;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[scan_prop]
|
||||
pub enum WidgetNode {
|
||||
Label { props: Map },
|
||||
Box { props: Map, children: Vec<WidgetNode> },
|
||||
CenterBox { props: Map, children: Vec<WidgetNode> },
|
||||
FlowBox { props: Map, children: Vec<WidgetNode> },
|
||||
Button { props: Map },
|
||||
Image { props: Map },
|
||||
Input { props: Map },
|
||||
Progress { props: Map },
|
||||
ComboBoxText { props: Map },
|
||||
Slider { props: Map },
|
||||
Scale { props: Map },
|
||||
Checkbox { props: Map },
|
||||
Expander { props: Map, children: Vec<WidgetNode> },
|
||||
Revealer { props: Map, children: Vec<WidgetNode> },
|
||||
@@ -30,6 +32,11 @@ pub enum WidgetNode {
|
||||
EventBox { props: Map, children: Vec<WidgetNode> },
|
||||
ToolTip { props: Map, children: Vec<WidgetNode> },
|
||||
|
||||
// Special
|
||||
LocalBind { props: Map, children: Vec<WidgetNode> },
|
||||
WidgetAction { props: Map, children: Vec<WidgetNode> },
|
||||
GtkUI { props: Map },
|
||||
|
||||
// Top-level macros
|
||||
DefWindow { name: String, props: Map, node: Box<WidgetNode> },
|
||||
// Poll { var: String, interval: String, cmd: String, initial: String },
|
||||
@@ -72,16 +79,9 @@ pub fn get_id_to_widget_info<'a>(
|
||||
get_id_to_widget_info(child, id_to_props, Some(id))?;
|
||||
}
|
||||
}
|
||||
WidgetNode::CenterBox { props, children } => {
|
||||
let id = hash_props_and_type(props, "CenterBox");
|
||||
insert_wdgt_info(
|
||||
node,
|
||||
props,
|
||||
"CenterBox",
|
||||
children.as_slice(),
|
||||
parent_id,
|
||||
id_to_props,
|
||||
)?;
|
||||
WidgetNode::FlowBox { props, children } => {
|
||||
let id = hash_props_and_type(props, "FlowBox");
|
||||
insert_wdgt_info(node, props, "FlowBox", children.as_slice(), parent_id, id_to_props)?;
|
||||
for child in children {
|
||||
get_id_to_widget_info(child, id_to_props, Some(id))?;
|
||||
}
|
||||
@@ -105,9 +105,9 @@ pub fn get_id_to_widget_info<'a>(
|
||||
// let id = hash_props_and_type(props, "Transform");
|
||||
insert_wdgt_info(node, props, "Transform", &[], parent_id, id_to_props)?;
|
||||
}
|
||||
WidgetNode::Slider { props } => {
|
||||
// let id = hash_props_and_type(props, "Slider");
|
||||
insert_wdgt_info(node, props, "Slider", &[], parent_id, id_to_props)?;
|
||||
WidgetNode::Scale { props } => {
|
||||
// let id = hash_props_and_type(props, "Scale");
|
||||
insert_wdgt_info(node, props, "Scale", &[], parent_id, id_to_props)?;
|
||||
}
|
||||
WidgetNode::Progress { props } => {
|
||||
// let id = hash_props_and_type(props, "Progress");
|
||||
@@ -151,6 +151,37 @@ pub fn get_id_to_widget_info<'a>(
|
||||
get_id_to_widget_info(child, id_to_props, Some(id))?;
|
||||
}
|
||||
}
|
||||
WidgetNode::LocalBind { props, children } => {
|
||||
let id = hash_props_and_type(props, "LocalBind");
|
||||
insert_wdgt_info(
|
||||
node,
|
||||
props,
|
||||
"LocalBind",
|
||||
children.as_slice(),
|
||||
parent_id,
|
||||
id_to_props,
|
||||
)?;
|
||||
for child in children {
|
||||
get_id_to_widget_info(child, id_to_props, Some(id))?;
|
||||
}
|
||||
}
|
||||
WidgetNode::WidgetAction { props, children } => {
|
||||
let id = hash_props_and_type(props, "WidgetAction");
|
||||
insert_wdgt_info(
|
||||
node,
|
||||
props,
|
||||
"WidgetAction",
|
||||
children.as_slice(),
|
||||
parent_id,
|
||||
id_to_props,
|
||||
)?;
|
||||
for child in children {
|
||||
get_id_to_widget_info(child, id_to_props, Some(id))?;
|
||||
}
|
||||
}
|
||||
WidgetNode::GtkUI { props } => {
|
||||
insert_wdgt_info(node, props, "GtkUI", &[], parent_id, id_to_props)?;
|
||||
}
|
||||
WidgetNode::ColorChooser { props } => {
|
||||
// let id = hash_props_and_type(props, "ColorChooser");
|
||||
insert_wdgt_info(node, props, "ColorChooser", &[], parent_id, id_to_props)?;
|
||||
@@ -227,6 +258,14 @@ pub fn hash_props_and_type(props: &Map, widget_type_str: &str) -> u64 {
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
pub fn hash_props(props: &Map) -> u64 {
|
||||
let mut hasher = AHasher::default();
|
||||
|
||||
props.hash(&mut hasher);
|
||||
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::ast::WidgetNode;
|
||||
use crate::ast::{hash_props, WidgetNode};
|
||||
use crate::updates::{register_signal, LocalDataBinder, LocalSignal};
|
||||
use rhai::{Array, Engine, EvalAltResult, Map, NativeCallContext};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Converts a Dynamic array into a Vec<WidgetNode>, returning proper errors with position.
|
||||
fn children_to_vec(
|
||||
@@ -22,8 +24,13 @@ fn children_to_vec(
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn register_all_widgets(engine: &mut Engine, all_nodes: &Rc<RefCell<Vec<WidgetNode>>>) {
|
||||
pub fn register_all_widgets(
|
||||
engine: &mut Engine,
|
||||
all_nodes: &Rc<RefCell<Vec<WidgetNode>>>,
|
||||
keep_signal: &Rc<RefCell<Vec<u64>>>,
|
||||
) {
|
||||
engine.register_type::<WidgetNode>();
|
||||
engine.register_type::<LocalSignal>();
|
||||
|
||||
// == Primitive widgets ==
|
||||
macro_rules! register_primitive {
|
||||
@@ -40,7 +47,7 @@ pub fn register_all_widgets(engine: &mut Engine, all_nodes: &Rc<RefCell<Vec<Widg
|
||||
register_primitive!("input", Input);
|
||||
register_primitive!("progress", Progress);
|
||||
register_primitive!("combo_box_text", ComboBoxText);
|
||||
register_primitive!("slider", Slider);
|
||||
register_primitive!("scale", Scale);
|
||||
register_primitive!("checkbox", Checkbox);
|
||||
register_primitive!("calendar", Calendar);
|
||||
register_primitive!("graph", Graph);
|
||||
@@ -66,7 +73,7 @@ pub fn register_all_widgets(engine: &mut Engine, all_nodes: &Rc<RefCell<Vec<Widg
|
||||
}
|
||||
|
||||
register_with_children!("box", Box);
|
||||
register_with_children!("centerbox", CenterBox);
|
||||
register_with_children!("flowbox", FlowBox);
|
||||
register_with_children!("expander", Expander);
|
||||
register_with_children!("revealer", Revealer);
|
||||
register_with_children!("scroll", Scroll);
|
||||
@@ -74,6 +81,35 @@ pub fn register_all_widgets(engine: &mut Engine, all_nodes: &Rc<RefCell<Vec<Widg
|
||||
register_with_children!("stack", Stack);
|
||||
register_with_children!("eventbox", EventBox);
|
||||
register_with_children!("tooltip", ToolTip);
|
||||
register_with_children!("localbind", LocalBind);
|
||||
register_with_children!("widget_action", WidgetAction);
|
||||
|
||||
// == Special widget
|
||||
engine.register_fn(
|
||||
"gtk_ui",
|
||||
|path: &str, load: &str| -> Result<WidgetNode, Box<EvalAltResult>> {
|
||||
let mut props = Map::new();
|
||||
props.insert("file".into(), path.into());
|
||||
props.insert("id".into(), load.into());
|
||||
Ok(WidgetNode::GtkUI { props })
|
||||
},
|
||||
);
|
||||
|
||||
// == Special signal
|
||||
let keep_signal_clone = keep_signal.clone();
|
||||
engine.register_fn(
|
||||
"localsignal",
|
||||
move |props: Map| -> Result<LocalSignal, Box<EvalAltResult>> {
|
||||
let id = hash_props(&props);
|
||||
let signal = Rc::new(LocalSignal { id, props, data: Arc::new(LocalDataBinder::new()) });
|
||||
|
||||
let signal_rc = register_signal(id, signal);
|
||||
|
||||
keep_signal_clone.borrow_mut().push(id);
|
||||
|
||||
Ok((*signal_rc).clone())
|
||||
},
|
||||
);
|
||||
|
||||
// == Top-level macros ==
|
||||
engine.register_fn(
|
||||
|
||||
@@ -4,7 +4,7 @@ use rhai::{Dynamic, Map};
|
||||
impl WidgetNode {
|
||||
/// A very important implementation of [`WidgetNode`].
|
||||
/// This function implements dyn_id property to widgets.
|
||||
pub fn setup_for_rt(&self, parent_path: &str) -> Self {
|
||||
pub fn setup_dyn_ids(&self, parent_path: &str) -> Self {
|
||||
// fn to assign dyn_id to a node
|
||||
fn with_dyn_id(mut props: Map, dyn_id: &str) -> Map {
|
||||
props.insert("dyn_id".into(), Dynamic::from(dyn_id.to_string()));
|
||||
@@ -22,7 +22,7 @@ impl WidgetNode {
|
||||
.enumerate()
|
||||
.map(|(idx, child)| {
|
||||
let child_path = format!("{}_{}_{}", parent_path, kind, idx);
|
||||
child.setup_for_rt(&child_path)
|
||||
child.setup_dyn_ids(&child_path)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -31,7 +31,7 @@ impl WidgetNode {
|
||||
WidgetNode::DefWindow { name, props, node } => WidgetNode::DefWindow {
|
||||
name: name.clone(),
|
||||
props: props.clone(),
|
||||
node: Box::new(node.setup_for_rt(name)),
|
||||
node: Box::new(node.setup_dyn_ids(name)),
|
||||
},
|
||||
|
||||
// == Containers with children ==
|
||||
@@ -39,9 +39,9 @@ impl WidgetNode {
|
||||
props: with_dyn_id(props.clone(), parent_path),
|
||||
children: process_children(children, parent_path, "box"),
|
||||
},
|
||||
WidgetNode::CenterBox { props, children } => WidgetNode::CenterBox {
|
||||
WidgetNode::FlowBox { props, children } => WidgetNode::FlowBox {
|
||||
props: with_dyn_id(props.clone(), parent_path),
|
||||
children: process_children(children, parent_path, "centerbox"),
|
||||
children: process_children(children, parent_path, "flowbox"),
|
||||
},
|
||||
WidgetNode::Expander { props, children } => WidgetNode::Expander {
|
||||
props: with_dyn_id(props.clone(), parent_path),
|
||||
@@ -71,6 +71,14 @@ impl WidgetNode {
|
||||
props: with_dyn_id(props.clone(), parent_path),
|
||||
children: process_children(children, parent_path, "tooltip"),
|
||||
},
|
||||
WidgetNode::LocalBind { props, children } => WidgetNode::LocalBind {
|
||||
props: with_dyn_id(props.clone(), parent_path),
|
||||
children: process_children(children, parent_path, "localbind"),
|
||||
},
|
||||
WidgetNode::WidgetAction { props, children } => WidgetNode::WidgetAction {
|
||||
props: with_dyn_id(props.clone(), parent_path),
|
||||
children: process_children(children, parent_path, "widget_action"),
|
||||
},
|
||||
|
||||
// == Top-level container for multiple widgets ==
|
||||
WidgetNode::Enter(children) => {
|
||||
@@ -94,13 +102,14 @@ impl WidgetNode {
|
||||
| node @ WidgetNode::Input { props }
|
||||
| node @ WidgetNode::Progress { props }
|
||||
| node @ WidgetNode::ComboBoxText { props }
|
||||
| node @ WidgetNode::Slider { props }
|
||||
| node @ WidgetNode::Scale { props }
|
||||
| node @ WidgetNode::Checkbox { props }
|
||||
| node @ WidgetNode::Calendar { props }
|
||||
| node @ WidgetNode::ColorButton { props }
|
||||
| node @ WidgetNode::ColorChooser { props }
|
||||
| node @ WidgetNode::CircularProgress { props }
|
||||
| node @ WidgetNode::Graph { props }
|
||||
| node @ WidgetNode::GtkUI { props }
|
||||
| node @ WidgetNode::Transform { props } => {
|
||||
let new_props = with_dyn_id(props.clone(), parent_path);
|
||||
match node {
|
||||
@@ -112,7 +121,7 @@ impl WidgetNode {
|
||||
WidgetNode::ComboBoxText { .. } => {
|
||||
WidgetNode::ComboBoxText { props: new_props }
|
||||
}
|
||||
WidgetNode::Slider { .. } => WidgetNode::Slider { props: new_props },
|
||||
WidgetNode::Scale { .. } => WidgetNode::Scale { props: new_props },
|
||||
WidgetNode::Checkbox { .. } => WidgetNode::Checkbox { props: new_props },
|
||||
WidgetNode::Calendar { .. } => WidgetNode::Calendar { props: new_props },
|
||||
WidgetNode::ColorButton { .. } => WidgetNode::ColorButton { props: new_props },
|
||||
@@ -123,6 +132,7 @@ impl WidgetNode {
|
||||
WidgetNode::CircularProgress { props: new_props }
|
||||
}
|
||||
WidgetNode::Graph { .. } => WidgetNode::Graph { props: new_props },
|
||||
WidgetNode::GtkUI { .. } => WidgetNode::GtkUI { props: new_props },
|
||||
WidgetNode::Transform { .. } => WidgetNode::Transform { props: new_props },
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
@@ -5,34 +5,51 @@ use rhai::{Engine, EvalAltResult, ParseError};
|
||||
use rhai_trace::{BetterError, Span};
|
||||
|
||||
/// Return a formatted Rhai evaluation error.
|
||||
pub fn format_eval_error(error: &EvalAltResult, code: &str, engine: &Engine) -> String {
|
||||
pub fn format_eval_error(
|
||||
error: &EvalAltResult,
|
||||
code: &str,
|
||||
engine: &Engine,
|
||||
file_id: Option<&str>,
|
||||
) -> String {
|
||||
let error_str = error.to_string();
|
||||
|
||||
if error_str == "" || error_str == "module_eval_failed" || error_str == "module_parse_failed" {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let better_error =
|
||||
BetterError::improve_eval_error(error, code, engine, None).unwrap_or(BetterError {
|
||||
message: error.to_string(),
|
||||
message: error_str,
|
||||
help: None,
|
||||
hint: None,
|
||||
note: None,
|
||||
span: Span::new(0, 0, 0, 0),
|
||||
});
|
||||
format_codespan_error(better_error, code)
|
||||
format_codespan_error(better_error, code, file_id)
|
||||
}
|
||||
|
||||
/// Return a formatted Rhai parse error.
|
||||
pub fn format_parse_error(error: &ParseError, code: &str) -> String {
|
||||
pub fn format_parse_error(error: &ParseError, code: &str, file_id: Option<&str>) -> String {
|
||||
let error_str = error.to_string();
|
||||
|
||||
if error_str == "" || error_str == "module_eval_failed" || error_str == "module_parse_failed" {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let better_error = BetterError::improve_parse_error(error, code).unwrap_or(BetterError {
|
||||
message: error.to_string(),
|
||||
message: error_str,
|
||||
help: None,
|
||||
hint: None,
|
||||
note: None,
|
||||
span: Span::new(0, 0, 0, 0),
|
||||
});
|
||||
format_codespan_error(better_error, code)
|
||||
format_codespan_error(better_error, code, file_id)
|
||||
}
|
||||
|
||||
/// Return a formatted error as a String
|
||||
pub fn format_codespan_error(be: BetterError, code: &str) -> String {
|
||||
pub fn format_codespan_error(be: BetterError, code: &str, file_id: Option<&str>) -> String {
|
||||
let mut files = SimpleFiles::new();
|
||||
let file_id = files.add("<rhai>", code);
|
||||
let file_id = files.add(file_id.unwrap_or("<rhai>"), code);
|
||||
|
||||
// build the notes
|
||||
let mut notes = Vec::new();
|
||||
@@ -47,12 +64,15 @@ pub fn format_codespan_error(be: BetterError, code: &str) -> String {
|
||||
}
|
||||
|
||||
// build the diagnostic error
|
||||
let diagnostic = Diagnostic::error()
|
||||
.with_message(&be.message)
|
||||
.with_labels(vec![
|
||||
Label::primary(file_id, be.span.start()..be.span.end()).with_message(&be.message)
|
||||
])
|
||||
.with_notes(notes);
|
||||
let mut labels = Vec::new();
|
||||
if be.span.start() != be.span.end() {
|
||||
labels.push(
|
||||
Label::primary(file_id, be.span.start()..be.span.end()).with_message(&be.message),
|
||||
);
|
||||
}
|
||||
|
||||
let diagnostic =
|
||||
Diagnostic::error().with_message(&be.message).with_labels(labels).with_notes(notes);
|
||||
|
||||
let mut buffer = Buffer::ansi();
|
||||
let config = term::Config::default();
|
||||
|
||||
@@ -1,13 +1,44 @@
|
||||
use crate::error::format_eval_error;
|
||||
use anyhow::Result;
|
||||
use rhai::Engine;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
pub fn extract_poll_and_listen_vars(code: &str) -> Result<Vec<(String, Option<String>)>> {
|
||||
extract_poll_and_listen_vars_inner(code, &mut HashSet::new())
|
||||
}
|
||||
|
||||
fn extract_poll_and_listen_vars_inner(
|
||||
code: &str,
|
||||
visited: &mut HashSet<PathBuf>,
|
||||
) -> Result<Vec<(String, Option<String>)>> {
|
||||
let mut results = Vec::new();
|
||||
let mut engine = Engine::new();
|
||||
|
||||
register_temp_poll_listen(&mut engine);
|
||||
|
||||
// Handle imports manually
|
||||
for import_path in extract_import_paths(code)? {
|
||||
let resolved = resolve_import_path(&import_path)?;
|
||||
|
||||
// Prevent infinite recursion
|
||||
let canonical = fs::canonicalize(&resolved).unwrap_or(resolved.clone());
|
||||
if visited.contains(&canonical) {
|
||||
continue;
|
||||
}
|
||||
|
||||
visited.insert(canonical.clone());
|
||||
|
||||
if resolved.exists() {
|
||||
let imported_code = fs::read_to_string(&resolved)?;
|
||||
let inner = extract_poll_and_listen_vars_inner(&imported_code, visited)?;
|
||||
results.extend(inner);
|
||||
}
|
||||
}
|
||||
|
||||
// Process this file’s own poll/listen calls
|
||||
for expr in extract_poll_listen_exprs(code) {
|
||||
match engine.eval_expression::<TempSignal>(&expr) {
|
||||
Ok(sig) => {
|
||||
@@ -15,7 +46,7 @@ pub fn extract_poll_and_listen_vars(code: &str) -> Result<Vec<(String, Option<St
|
||||
results.push((sig.var, initial));
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(anyhow::anyhow!(format_eval_error(&e, code, &engine)));
|
||||
return Err(anyhow::anyhow!(format_eval_error(&e, code, &engine, None)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +54,38 @@ pub fn extract_poll_and_listen_vars(code: &str) -> Result<Vec<(String, Option<St
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Extract import paths from the Rhai source code
|
||||
fn extract_import_paths(code: &str) -> Result<Vec<String>> {
|
||||
let mut imports = Vec::new();
|
||||
|
||||
for line in code.lines() {
|
||||
let trimmed = line.trim_start();
|
||||
|
||||
if trimmed.starts_with("import ") {
|
||||
if let Some(start) = trimmed.find('"') {
|
||||
if let Some(end_rel) = trimmed[start + 1..].find('"') {
|
||||
let end = start + 1 + end_rel;
|
||||
let path = &trimmed[start + 1..end];
|
||||
imports.push(path.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(imports)
|
||||
}
|
||||
|
||||
/// Resolve relative and absolute import paths.
|
||||
fn resolve_import_path(import_path: &str) -> Result<PathBuf> {
|
||||
let path = Path::new(import_path);
|
||||
let abs =
|
||||
if path.is_absolute() { path.to_path_buf() } else { std::env::current_dir()?.join(path) };
|
||||
|
||||
let abs = if abs.extension().is_none() { abs.with_extension("rhai") } else { abs };
|
||||
|
||||
Ok(abs)
|
||||
}
|
||||
|
||||
pub fn extract_poll_listen_exprs(code: &str) -> Vec<String> {
|
||||
let mut exprs = Vec::new();
|
||||
let mut i = 0;
|
||||
@@ -35,7 +98,7 @@ pub fn extract_poll_listen_exprs(code: &str) -> Vec<String> {
|
||||
while i < len && code_bytes[i] as char != '\n' {
|
||||
i += 1;
|
||||
}
|
||||
i += 1; // skipp a full line
|
||||
i += 1; // skip a full line
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! IIRhai is a simple crate which configures rhai for the `ewwii` widget system.
|
||||
//! rhai_impl is a simple crate which configures rhai for the `ewwii` widget system.
|
||||
//!
|
||||
//! This crate supports parsing, error handling, and has a custom module_resolver.
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
use crate::error::{format_eval_error, format_parse_error};
|
||||
use crate::parser::ParseConfig;
|
||||
use rhai::{Engine, EvalAltResult, Module, ModuleResolver, Position, AST};
|
||||
use crate::updates::ReactiveVarStore;
|
||||
use rhai::Scope;
|
||||
use rhai::{Dynamic, Engine, EvalAltResult, Module, ModuleResolver, Position, AST};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct SimpleFileResolver;
|
||||
pub struct SimpleFileResolver {
|
||||
pub pl_handler_store: Option<ReactiveVarStore>,
|
||||
}
|
||||
|
||||
impl ModuleResolver for SimpleFileResolver {
|
||||
fn resolve(
|
||||
@@ -45,18 +50,51 @@ impl ModuleResolver for SimpleFileResolver {
|
||||
})?;
|
||||
|
||||
let ast: AST = engine.compile(&script).map_err(|e| {
|
||||
log::error!("{}", format_parse_error(&e, &script));
|
||||
e
|
||||
})?;
|
||||
let scope = ParseConfig::initial_poll_listen_scope(&script).map_err(|e| {
|
||||
EvalAltResult::ErrorSystem(
|
||||
format!("error setting up default variables: {full_path:?}"),
|
||||
e.into(),
|
||||
)
|
||||
Box::new(EvalAltResult::ErrorSystem(
|
||||
"module_parse_failed".into(),
|
||||
format_parse_error(&e, &script, full_path.to_str()).into(),
|
||||
))
|
||||
})?;
|
||||
|
||||
let parent_script: Option<String> = if let Some(parent_path) = source_path {
|
||||
match fs::read_to_string(parent_path) {
|
||||
Ok(s) => Some(s),
|
||||
Err(err) => {
|
||||
log::error!("Could not read parent script {parent_path:?}: {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut scope = if let Some(ref script) = parent_script {
|
||||
ParseConfig::initial_poll_listen_scope(script).map_err(|e| {
|
||||
EvalAltResult::ErrorSystem(
|
||||
format!("error setting up default variables from {source_path:?}"),
|
||||
e.into(),
|
||||
)
|
||||
})?
|
||||
} else {
|
||||
Scope::new()
|
||||
};
|
||||
|
||||
match &self.pl_handler_store {
|
||||
Some(val) => {
|
||||
let name_to_val: &HashMap<String, String> = &*val.read().unwrap();
|
||||
|
||||
for (name, val) in name_to_val {
|
||||
scope.set_value(name.clone(), Dynamic::from(val.clone()));
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
let mut module = Module::eval_ast_as_new(scope, &ast, engine).map_err(|e| {
|
||||
log::error!("{}", format_eval_error(&e, &script, engine));
|
||||
e
|
||||
Box::new(EvalAltResult::ErrorSystem(
|
||||
"module_eval_failed".into(),
|
||||
format_eval_error(&e, &script, engine, full_path.to_str()).into(),
|
||||
))
|
||||
})?;
|
||||
|
||||
module.build_index();
|
||||
@@ -80,6 +118,12 @@ impl<R1: ModuleResolver, R2: ModuleResolver> ModuleResolver for ChainedResolver<
|
||||
match self.first.resolve(engine, source_path, path, pos) {
|
||||
Ok(m) => Ok(m),
|
||||
Err(e1) => {
|
||||
if let EvalAltResult::ErrorSystem(msg, _) = e1.as_ref() {
|
||||
if msg == "module_eval_failed" || msg == "module_parse_failed" {
|
||||
return Err(e1);
|
||||
}
|
||||
}
|
||||
|
||||
log::trace!(
|
||||
"Error executing resolver 1, falling back to resolver 2. Error details: {}",
|
||||
e1
|
||||
|
||||
@@ -5,40 +5,45 @@ use crate::{
|
||||
helper::extract_poll_and_listen_vars,
|
||||
module_resolver::SimpleFileResolver,
|
||||
providers::register_all_providers,
|
||||
updates::ReactiveVarStore,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use rhai::{Dynamic, Engine, Scope, AST};
|
||||
use rhai::{Dynamic, Engine, ImmutableString, OptimizationLevel, Scope, AST};
|
||||
use std::cell::RefCell;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct ParseConfig {
|
||||
engine: Engine,
|
||||
pub engine: Engine,
|
||||
all_nodes: Rc<RefCell<Vec<WidgetNode>>>,
|
||||
keep_signal: Rc<RefCell<Vec<u64>>>,
|
||||
}
|
||||
|
||||
impl ParseConfig {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(pl_handler_store: Option<ReactiveVarStore>) -> Self {
|
||||
let mut engine = Engine::new();
|
||||
let all_nodes = Rc::new(RefCell::new(Vec::new()));
|
||||
let keep_signal = Rc::new(RefCell::new(Vec::new()));
|
||||
|
||||
engine.set_max_expr_depths(128, 128);
|
||||
engine.set_module_resolver(SimpleFileResolver);
|
||||
engine
|
||||
.set_module_resolver(SimpleFileResolver { pl_handler_store: pl_handler_store.clone() });
|
||||
|
||||
register_all_widgets(&mut engine, &all_nodes);
|
||||
register_all_providers(&mut engine);
|
||||
register_all_widgets(&mut engine, &all_nodes, &keep_signal);
|
||||
register_all_providers(&mut engine, pl_handler_store);
|
||||
|
||||
Self { engine, all_nodes }
|
||||
Self { engine, all_nodes, keep_signal }
|
||||
}
|
||||
|
||||
pub fn eval_code(&mut self, code: &str) -> Result<WidgetNode> {
|
||||
let ast = self.engine.compile(code)?;
|
||||
self.eval_code_with(code, None, Some(&ast))
|
||||
}
|
||||
pub fn compile_code(&mut self, code: &str, file_path: &str) -> Result<AST> {
|
||||
let mut ast = self
|
||||
.engine
|
||||
.compile(code)
|
||||
.map_err(|e| anyhow!(format_parse_error(&e, code, Some(file_path))))?;
|
||||
|
||||
pub fn compile_code(&mut self, code: &str) -> Result<AST> {
|
||||
self.engine.compile(code).map_err(|e| anyhow!(format_parse_error(&e, code)))
|
||||
ast.set_source(ImmutableString::from(file_path));
|
||||
Ok(ast)
|
||||
}
|
||||
|
||||
pub fn eval_code_with(
|
||||
@@ -46,6 +51,7 @@ impl ParseConfig {
|
||||
code: &str,
|
||||
rhai_scope: Option<Scope>,
|
||||
compiled_ast: Option<&AST>,
|
||||
file_id: Option<&str>,
|
||||
) -> Result<WidgetNode> {
|
||||
let mut scope = match rhai_scope {
|
||||
Some(s) => s,
|
||||
@@ -57,14 +63,17 @@ impl ParseConfig {
|
||||
let _ = self
|
||||
.engine
|
||||
.eval_ast_with_scope::<Dynamic>(&mut scope, &ast)
|
||||
.map_err(|e| anyhow!(format_eval_error(&e, code, &self.engine)))?;
|
||||
.map_err(|e| anyhow!(format_eval_error(&e, code, &self.engine, file_id)))?;
|
||||
} else {
|
||||
let _ = self
|
||||
.engine
|
||||
.eval_with_scope::<Dynamic>(&mut scope, code)
|
||||
.map_err(|e| anyhow!(format_eval_error(&e, code, &self.engine)))?;
|
||||
.map_err(|e| anyhow!(format_eval_error(&e, code, &self.engine, file_id)))?;
|
||||
};
|
||||
|
||||
// Retain signals
|
||||
crate::updates::retain_signals(&self.keep_signal.borrow());
|
||||
|
||||
// Merge all nodes in all_nodes (`enter([])`) into a single root node
|
||||
let merged_node = {
|
||||
let mut all_nodes_vec = self.all_nodes.borrow_mut();
|
||||
@@ -83,7 +92,25 @@ impl ParseConfig {
|
||||
WidgetNode::Enter(merged_children)
|
||||
};
|
||||
|
||||
Ok(merged_node.setup_for_rt("root"))
|
||||
Ok(merged_node.setup_dyn_ids("root"))
|
||||
}
|
||||
|
||||
pub fn eval_code_snippet(&mut self, code: &str) -> Result<WidgetNode> {
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Just eval as node will be in `all_nodes`
|
||||
let node = self
|
||||
.engine
|
||||
.eval_with_scope::<WidgetNode>(&mut scope, code)
|
||||
.map_err(|e| anyhow!(format_eval_error(&e, code, &self.engine, Some("<dyn eval>"))))?;
|
||||
|
||||
// Retain signals
|
||||
crate::updates::retain_signals(&self.keep_signal.borrow());
|
||||
|
||||
// Clear all nodes
|
||||
self.all_nodes.borrow_mut().clear();
|
||||
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
pub fn code_from_file<P: AsRef<Path>>(&mut self, file_path: P) -> Result<String> {
|
||||
@@ -144,4 +171,15 @@ impl ParseConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_opt_level(&mut self, opt_lvl: OptimizationLevel) {
|
||||
self.engine.set_optimization_level(opt_lvl);
|
||||
}
|
||||
|
||||
pub fn action_with_engine<F, R>(&mut self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Engine) -> R,
|
||||
{
|
||||
f(&mut self.engine)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ pub mod linux {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "api::linux" as linux;
|
||||
///
|
||||
/// let k_version = linux::get_kernel_version();
|
||||
@@ -44,7 +44,7 @@ pub mod linux {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "api::linux" as linux;
|
||||
///
|
||||
/// let battery_perc = linux::get_battery_perc();
|
||||
@@ -88,7 +88,7 @@ pub mod linux {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "api::linux" as linux;
|
||||
///
|
||||
/// let cpu_info = linux::get_cpu_info();
|
||||
@@ -143,7 +143,7 @@ pub mod linux {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "api::linux" as linux;
|
||||
///
|
||||
/// let ram_info = linux::get_ram_info();
|
||||
@@ -201,7 +201,7 @@ pub mod linux {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "api::linux" as linux;
|
||||
///
|
||||
/// let gpu_info = linux::get_gpu_info();
|
||||
@@ -272,7 +272,7 @@ pub mod linux {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "api::linux" as linux;
|
||||
///
|
||||
/// let disk_info = linux::get_disk_info();
|
||||
|
||||
@@ -21,7 +21,7 @@ pub mod wifi {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "api::wifi" as wifi;
|
||||
///
|
||||
/// let networks = wifi::scan();
|
||||
@@ -66,7 +66,7 @@ pub mod wifi {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "api::wifi" as wifi;
|
||||
///
|
||||
/// let networks = wifi::scan();
|
||||
@@ -111,7 +111,7 @@ pub mod wifi {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "api::wifi" as wifi;
|
||||
///
|
||||
/// let networks = wifi::scan();
|
||||
@@ -146,7 +146,7 @@ pub mod wifi {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "api::wifi" as wifi;
|
||||
///
|
||||
/// let connection = wifi::current_connection();
|
||||
@@ -225,7 +225,7 @@ pub mod wifi {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "api::wifi" as wifi;
|
||||
///
|
||||
/// wifi::connect("MySecretNetwork", "password123");
|
||||
@@ -277,7 +277,7 @@ pub mod wifi {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "api::wifi" as wifi;
|
||||
///
|
||||
/// wifi::connect_without_password("MySecretNetwork", "password123");
|
||||
@@ -329,7 +329,7 @@ pub mod wifi {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "api::wifi" as wifi;
|
||||
///
|
||||
/// wifi::disconnect();
|
||||
@@ -402,7 +402,7 @@ pub mod wifi {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "api::wifi" as wifi;
|
||||
///
|
||||
/// wifi::disable_adapter();
|
||||
@@ -453,7 +453,7 @@ pub mod wifi {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "api::wifi" as wifi;
|
||||
///
|
||||
/// wifi::enable_adapter();
|
||||
@@ -516,7 +516,7 @@ pub mod wifi {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "api::wifi" as wifi;
|
||||
///
|
||||
/// wifi::enable_adapter();
|
||||
|
||||
@@ -11,19 +11,23 @@ mod apilib;
|
||||
mod stdlib;
|
||||
|
||||
use crate::module_resolver::{ChainedResolver, SimpleFileResolver};
|
||||
use crate::updates::ReactiveVarStore;
|
||||
use rhai::module_resolvers::StaticModuleResolver;
|
||||
|
||||
// expose the api's publically
|
||||
pub use apilib::register_apilib;
|
||||
pub use stdlib::register_stdlib;
|
||||
|
||||
pub fn register_all_providers(engine: &mut rhai::Engine) {
|
||||
pub fn register_all_providers(engine: &mut rhai::Engine, plhs: Option<ReactiveVarStore>) {
|
||||
let mut resolver = StaticModuleResolver::new();
|
||||
|
||||
// modules
|
||||
register_stdlib(&mut resolver);
|
||||
register_apilib(&mut resolver);
|
||||
|
||||
let chained = ChainedResolver { first: SimpleFileResolver, second: resolver.clone() };
|
||||
let chained = ChainedResolver {
|
||||
first: SimpleFileResolver { pl_handler_store: plhs.clone() },
|
||||
second: resolver.clone(),
|
||||
};
|
||||
engine.set_module_resolver(chained);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ pub mod command {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "std::command" as cmd;
|
||||
///
|
||||
/// // Run a shell command (e.g., list directory contents)
|
||||
@@ -46,7 +46,7 @@ pub mod command {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "std::command" as cmd;
|
||||
///
|
||||
/// // Run a shell command and capture its output
|
||||
|
||||
@@ -16,7 +16,7 @@ pub mod env {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "std::env" as env;
|
||||
///
|
||||
/// // Get the value of the "HOME" environment variable
|
||||
@@ -41,7 +41,7 @@ pub mod env {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "std::env" as env;
|
||||
///
|
||||
/// // Set the value of the "MY_VAR" environment variable
|
||||
@@ -60,7 +60,7 @@ pub mod env {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "std::env" as env;
|
||||
///
|
||||
/// // Get the home directory
|
||||
@@ -81,7 +81,7 @@ pub mod env {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "std::env" as env;
|
||||
///
|
||||
/// // Get the current working directory
|
||||
@@ -106,7 +106,7 @@ pub mod env {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "std::env" as env;
|
||||
///
|
||||
/// // Get the username of the current user
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
pub mod command;
|
||||
pub mod env;
|
||||
pub mod monitor;
|
||||
pub mod regex;
|
||||
pub mod text;
|
||||
|
||||
use rhai::exported_module;
|
||||
use rhai::module_resolvers::StaticModuleResolver;
|
||||
|
||||
pub fn register_stdlib(resolver: &mut StaticModuleResolver) {
|
||||
use crate::providers::stdlib::{command::command, env::env, monitor::monitor, text::text};
|
||||
use crate::providers::stdlib::{command::command, env::env, regex::regex_lib, text::text};
|
||||
|
||||
// adding modules
|
||||
let text_mod = exported_module!(text);
|
||||
let env_mod = exported_module!(env);
|
||||
let monitor_mod = exported_module!(monitor);
|
||||
let command_mod = exported_module!(command);
|
||||
let regex_mod = exported_module!(regex_lib);
|
||||
|
||||
// inserting modules
|
||||
resolver.insert("std::text", text_mod);
|
||||
resolver.insert("std::env", env_mod);
|
||||
resolver.insert("std::monitor", monitor_mod);
|
||||
resolver.insert("std::command", command_mod);
|
||||
resolver.insert("std::regex", regex_mod);
|
||||
}
|
||||
|
||||
@@ -1,249 +0,0 @@
|
||||
use gtk::gdk;
|
||||
use gtk::prelude::*;
|
||||
use rhai::plugin::*;
|
||||
|
||||
#[export_module]
|
||||
pub mod monitor {
|
||||
/// Get the number of connected monitors.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the total number of connected monitors as an `i64`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// import "std::monitor" as monitor;
|
||||
///
|
||||
/// let count = monitor::count();
|
||||
/// print(count); // Output: Number of connected monitors
|
||||
/// ```
|
||||
pub fn count() -> i64 {
|
||||
get_monitor_count()
|
||||
}
|
||||
|
||||
/// Get the resolution of the primary monitor.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns an array containing the width and height of the primary monitor as two `i64` values.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// import "std::monitor" as monitor;
|
||||
///
|
||||
/// let resolution = monitor::primary_resolution();
|
||||
/// print(resolution); // Output: [width, height]
|
||||
/// ```
|
||||
pub fn primary_resolution() -> [i64; 2] {
|
||||
let (w, h) = get_primary_monitor_resolution();
|
||||
[w, h]
|
||||
}
|
||||
|
||||
/// Get the resolution of the primary monitor as a string.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the resolution of the primary monitor as a string in the format "width x height".
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// import "std::monitor" as monitor;
|
||||
///
|
||||
/// let resolution_str = monitor::primary_resolution_str();
|
||||
/// print(resolution_str); // Output: "1920x1080"
|
||||
/// ```
|
||||
pub fn primary_resolution_str() -> String {
|
||||
let (w, h) = get_primary_monitor_resolution();
|
||||
format!("{w}x{h}")
|
||||
}
|
||||
|
||||
/// Get the resolutions of all connected monitors.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns an array of arrays, where each inner array contains the width and height of a monitor.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// import "std::monitor" as monitor;
|
||||
///
|
||||
/// let resolutions = monitor::all_resolutions();
|
||||
/// print(resolutions); // Output: [[width1, height1], [width2, height2], ...]
|
||||
/// ```
|
||||
pub fn all_resolutions() -> Vec<[i64; 2]> {
|
||||
get_all_monitor_resolutions().into_iter().map(|(w, h)| [w, h]).collect()
|
||||
}
|
||||
|
||||
/// Get the resolutions of all connected monitors as a string.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a string where each monitor's resolution is formatted as "width x height", separated by commas.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// import "std::monitor" as monitor;
|
||||
///
|
||||
/// let resolutions_str = monitor::all_resolutions_str();
|
||||
/// print(resolutions_str); // Output: "1920x1080, 1280x720"
|
||||
/// ```
|
||||
pub fn all_resolutions_str() -> String {
|
||||
get_all_monitor_resolutions()
|
||||
.into_iter()
|
||||
.map(|(w, h)| format!("{w}x{h}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
/// Get the dimensions (x, y, width, height) of a specific monitor.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `index` - The index of the monitor (0-based).
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns an array with the monitor's position (x, y) and size (width, height).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// import "std::monitor" as monitor;
|
||||
///
|
||||
/// let dimensions = monitor::dimensions(0);
|
||||
/// print(dimensions); // Output: [x, y, width, height]
|
||||
/// ```
|
||||
pub fn dimensions(index: i64) -> [i64; 4] {
|
||||
let (x, y, w, h) = get_monitor_dimensions(index as usize);
|
||||
[x, y, w, h]
|
||||
}
|
||||
|
||||
/// Get the dimensions of a specific monitor as a string.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `index` - The index of the monitor (0-based).
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the monitor's dimensions as a string in the format "x,y - width x height".
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// import "std::monitor" as monitor;
|
||||
///
|
||||
/// let dimensions_str = monitor::dimensions_str(0);
|
||||
/// print(dimensions_str); // Output: "0,0 - 1920x1080"
|
||||
/// ```
|
||||
pub fn dimensions_str(index: i64) -> String {
|
||||
let (x, y, w, h) = get_monitor_dimensions(index as usize);
|
||||
format!("{x},{y} - {w}x{h}")
|
||||
}
|
||||
|
||||
/// Get the DPI (dots per inch) of a specific monitor.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `index` - The index of the monitor (0-based).
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the DPI (scale factor * base DPI) of the monitor as a `f64`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// import "std::monitor" as monitor;
|
||||
///
|
||||
/// let dpi = monitor::dpi(0);
|
||||
/// print(dpi); // Output: DPI of the monitor
|
||||
/// ```
|
||||
pub fn dpi(index: i64) -> f64 {
|
||||
get_monitor_dpi(index as usize)
|
||||
}
|
||||
|
||||
/// Get the DPI of a specific monitor as a string.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `index` - The index of the monitor (0-based).
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the DPI of the monitor as a string formatted to 1 decimal place.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// import "std::monitor" as monitor;
|
||||
///
|
||||
/// let dpi_str = monitor::dpi_str(0);
|
||||
/// print(dpi_str); // Output: "96.0"
|
||||
/// ```
|
||||
pub fn dpi_str(index: i64) -> String {
|
||||
format!("{:.1}", get_monitor_dpi(index as usize))
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_gdk_init() {
|
||||
if gtk::is_initialized_main_thread() {
|
||||
return;
|
||||
}
|
||||
gtk::init().expect("Failed to initialize GTK");
|
||||
}
|
||||
|
||||
fn get_monitor_count() -> i64 {
|
||||
ensure_gdk_init();
|
||||
let display = gdk::Display::default().expect("No display found");
|
||||
display.n_monitors() as i64
|
||||
}
|
||||
|
||||
fn get_primary_monitor_resolution() -> (i64, i64) {
|
||||
ensure_gdk_init();
|
||||
let display = gdk::Display::default().expect("No display found");
|
||||
if let Some(primary) = display.primary_monitor() {
|
||||
let rect = primary.geometry();
|
||||
(rect.width() as i64, rect.height() as i64)
|
||||
} else {
|
||||
(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_all_monitor_resolutions() -> Vec<(i64, i64)> {
|
||||
ensure_gdk_init();
|
||||
let display = gdk::Display::default().expect("No display found");
|
||||
(0..display.n_monitors())
|
||||
.filter_map(|i| display.monitor(i))
|
||||
.map(|m| {
|
||||
let rect = m.geometry();
|
||||
(rect.width() as i64, rect.height() as i64)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_monitor_dimensions(index: usize) -> (i64, i64, i64, i64) {
|
||||
ensure_gdk_init();
|
||||
let display = gdk::Display::default().expect("No display found");
|
||||
if let Some(m) = display.monitor(index as i32) {
|
||||
let geom = m.geometry();
|
||||
(geom.x() as i64, geom.y() as i64, geom.width() as i64, geom.height() as i64)
|
||||
} else {
|
||||
(0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_monitor_dpi(index: usize) -> f64 {
|
||||
ensure_gdk_init();
|
||||
let display = gdk::Display::default().expect("No display found");
|
||||
if let Some(m) = display.monitor(index as i32) {
|
||||
m.scale_factor() as f64 * 96.0 // base DPI * scale factor
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
120
crates/rhai_impl/src/providers/stdlib/regex.rs
Normal file
120
crates/rhai_impl/src/providers/stdlib/regex.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
use regex::Regex;
|
||||
use rhai::plugin::*;
|
||||
use rhai::{Array, Dynamic};
|
||||
|
||||
/// A module providing regular expression support.
|
||||
#[export_module]
|
||||
pub mod regex_lib {
|
||||
/// Checks if a regex pattern matches the given text.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `text` - A string to be matched with the pattern.
|
||||
/// * `pattern` - The pattern to match on the string.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a boolean (true/false) based on if the pattern is matched on the text provided.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```javascript
|
||||
/// import "std::regex" as regex;
|
||||
///
|
||||
/// let result = regex::is_match("This is an example!", "example");
|
||||
/// if result == true {
|
||||
/// print("The pattern is matched!");
|
||||
/// }
|
||||
/// ```
|
||||
#[rhai_fn(return_raw)]
|
||||
pub fn is_match(text: &str, pattern: &str) -> Result<bool, Box<EvalAltResult>> {
|
||||
let re = Regex::new(pattern).map_err(|e| format!("Failed to read regex pattern: {}", e))?;
|
||||
Ok(re.is_match(text))
|
||||
}
|
||||
|
||||
/// Returns the first match of a regex pattern in the text.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `text` - A string to be matched with the pattern.
|
||||
/// * `pattern` - The pattern to match on the string.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a string which is the first match of a regex pattern.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```javascript
|
||||
/// import "std::regex" as regex;
|
||||
///
|
||||
/// let result = regex::find("This is an example!", `\be\w*\b`);
|
||||
/// print(result); // output: "example"
|
||||
/// ```
|
||||
#[rhai_fn(return_raw)]
|
||||
pub fn find(text: &str, pattern: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
let re = Regex::new(pattern).map_err(|e| format!("Failed to read regex pattern: {}", e))?;
|
||||
match re.find(text).map(|m| m.as_str().to_string()) {
|
||||
Some(s) => Ok(s),
|
||||
None => Ok(String::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all matches of a regex pattern in the text.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `text` - A string to be matched with the pattern.
|
||||
/// * `pattern` - The pattern to match on the string.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns aan array of strings containing the all the things that match the regex pattern in the provided text.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```javascript
|
||||
/// import "std::regex" as regex;
|
||||
///
|
||||
/// let result = regex::find("This is an example!", `\be\w*\b`);
|
||||
/// print(result); // output: ["example"]
|
||||
/// ```
|
||||
#[rhai_fn(return_raw)]
|
||||
pub fn find_all(text: &str, pattern: &str) -> Result<Array, Box<EvalAltResult>> {
|
||||
let re = Regex::new(pattern).map_err(|e| format!("Failed to read regex pattern: {}", e))?;
|
||||
let results: Array =
|
||||
re.find_iter(text).map(|m| Dynamic::from(m.as_str().to_string())).collect();
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Replaces all matches of a regex pattern with a replacement string.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `text` - A string to be matched with the pattern.
|
||||
/// * `pattern` - The pattern to match on the string.
|
||||
/// * `replacement` - A string that the matched pattern will get replaced with.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the provided text with the matches of the regex pattern replaced with the provided replacement argument.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```javascript
|
||||
/// import "std::regex" as regex;
|
||||
///
|
||||
/// let result = regex::replace("This is an example!", "an example", "a test");
|
||||
/// print(result); // output: "This is a test!"
|
||||
/// ```
|
||||
#[rhai_fn(return_raw)]
|
||||
pub fn replace(
|
||||
text: &str,
|
||||
pattern: &str,
|
||||
replacement: &str,
|
||||
) -> Result<String, Box<EvalAltResult>> {
|
||||
let re = Regex::new(pattern).map_err(|e| format!("Failed to read regex pattern: {}", e))?;
|
||||
Ok(re.replace_all(text, replacement).to_string())
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ pub mod text {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "std::text" as text;
|
||||
///
|
||||
/// let result = text::to_slug("Hello World!");
|
||||
@@ -48,7 +48,7 @@ pub mod text {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "std::text" as text;
|
||||
///
|
||||
/// let result = text::to_camel_case("hello world example");
|
||||
@@ -93,7 +93,7 @@ pub mod text {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "std::text" as text;
|
||||
///
|
||||
/// let result = text::truncate_chars("Hello World!", 5);
|
||||
@@ -118,7 +118,7 @@ pub mod text {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "std::text" as text;
|
||||
///
|
||||
/// let result = text::to_upper("hello");
|
||||
@@ -140,7 +140,7 @@ pub mod text {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```js
|
||||
/// ```javascript
|
||||
/// import "std::text" as text;
|
||||
///
|
||||
/// let result = text::to_lower("HELLO");
|
||||
|
||||
@@ -30,6 +30,7 @@ use tokio::sync::watch;
|
||||
pub fn handle_listen(
|
||||
var_name: String,
|
||||
props: &Map,
|
||||
shell: String,
|
||||
store: ReactiveVarStore,
|
||||
tx: tokio::sync::mpsc::UnboundedSender<String>,
|
||||
) {
|
||||
@@ -76,7 +77,7 @@ pub fn handle_listen(
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut child = unsafe {
|
||||
Command::new("/bin/sh")
|
||||
Command::new(&shell)
|
||||
.arg("-c")
|
||||
.arg(&cmd)
|
||||
// .kill_on_drop(true)
|
||||
@@ -86,9 +87,41 @@ pub fn handle_listen(
|
||||
.pre_exec(|| {
|
||||
let _ = setpgid(Pid::from_raw(0), Pid::from_raw(0));
|
||||
|
||||
// chatgpt gave me this thing saying that it will kill all
|
||||
// children when my program dies. I dont think this will work...
|
||||
libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM);
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) != 0 {
|
||||
log::error!(
|
||||
"prctl PR_SET_PDEATHSIG failed: {}",
|
||||
std::io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
{
|
||||
use libc::{c_int, c_void};
|
||||
|
||||
const PROC_PDEATHSIG_CTL: c_int = 11;
|
||||
let sig: c_int = libc::SIGTERM;
|
||||
if libc::procctl(
|
||||
libc::P_PID,
|
||||
0,
|
||||
PROC_PDEATHSIG_CTL,
|
||||
&sig as *const _ as *mut c_void,
|
||||
) != 0
|
||||
{
|
||||
log::error!(
|
||||
"procctl PROC_PDEATHSIG_CTL failed: {}",
|
||||
std::io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
// Perhaps make it a TODO?
|
||||
log::warn!("Parent-death signal is not supported on macOS system");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
|
||||
249
crates/rhai_impl/src/updates/localsignal.rs
Normal file
249
crates/rhai_impl/src/updates/localsignal.rs
Normal file
@@ -0,0 +1,249 @@
|
||||
use super::{get_prefered_shell, handle_listen, handle_poll};
|
||||
use crate::parser::ParseConfig;
|
||||
use gtk4::glib;
|
||||
use gtk4::prelude::*;
|
||||
use gtk4::subclass::prelude::*;
|
||||
use once_cell::sync::Lazy;
|
||||
use rhai::Map;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LocalDataBinder {
|
||||
pub value: RefCell<String>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for LocalDataBinder {
|
||||
const NAME: &'static str = "LocalDataBinder";
|
||||
type Type = super::LocalDataBinder;
|
||||
type ParentType = glib::Object;
|
||||
}
|
||||
|
||||
impl ObjectImpl for LocalDataBinder {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: once_cell::sync::Lazy<Vec<glib::ParamSpec>> =
|
||||
once_cell::sync::Lazy::new(|| {
|
||||
vec![glib::ParamSpecString::builder("value")
|
||||
.nick("Value")
|
||||
.blurb("The bound value")
|
||||
.default_value(None)
|
||||
.build()]
|
||||
});
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
match pspec.name() {
|
||||
"value" => self.value.borrow().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
match pspec.name() {
|
||||
"value" => {
|
||||
let val: Option<String> = value.get().unwrap();
|
||||
self.set_value(&self.obj(), val.unwrap_or_default());
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalDataBinder {
|
||||
pub fn set_value(&self, obj: &super::LocalDataBinder, val: String) {
|
||||
*self.value.borrow_mut() = val;
|
||||
obj.notify("value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct LocalDataBinder(ObjectSubclass<imp::LocalDataBinder>);
|
||||
}
|
||||
|
||||
impl LocalDataBinder {
|
||||
pub fn new() -> Self {
|
||||
glib::Object::new::<Self>()
|
||||
}
|
||||
|
||||
pub fn value(&self) -> String {
|
||||
self.imp().value.borrow().clone()
|
||||
}
|
||||
|
||||
pub fn set_value(&self, val: &str) {
|
||||
self.set_property("value", val);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalSignal {
|
||||
pub id: u64,
|
||||
pub props: Map,
|
||||
pub data: Arc<LocalDataBinder>,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static LOCAL_SIGNALS: Lazy<RefCell<HashMap<u64, Rc<LocalSignal>>>> =
|
||||
Lazy::new(|| RefCell::new(HashMap::new()));
|
||||
}
|
||||
|
||||
pub fn register_signal(id: u64, signal: Rc<LocalSignal>) -> Rc<LocalSignal> {
|
||||
LOCAL_SIGNALS.with(|registry| {
|
||||
let mut map = registry.borrow_mut();
|
||||
map.entry(id).or_insert_with(|| signal.clone()).clone()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn retain_signals(ids: &Vec<u64>) {
|
||||
LOCAL_SIGNALS.with(|registry| {
|
||||
let mut map = registry.borrow_mut();
|
||||
map.retain(|id, _| ids.contains(id));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn notify_all_localsignals() {
|
||||
LOCAL_SIGNALS.with(|registry| {
|
||||
let registry_ref = registry.borrow();
|
||||
|
||||
for (_, signal) in registry_ref.iter() {
|
||||
signal.data.notify("value");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn handle_localsignal_changes(
|
||||
parser: Rc<RefCell<ParseConfig>>,
|
||||
ast: Option<Rc<RefCell<rhai::AST>>>,
|
||||
) {
|
||||
let shell = get_prefered_shell();
|
||||
let get_string_fn = shared_utils::extract_props::get_string_prop;
|
||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<String>();
|
||||
let store = Arc::new(RwLock::new(HashMap::new()));
|
||||
|
||||
LOCAL_SIGNALS.with(|registry| {
|
||||
let registry_ref = registry.borrow();
|
||||
|
||||
for (id, signal) in registry_ref.iter() {
|
||||
let props = &signal.props;
|
||||
|
||||
if let Ok(initial_str) = get_string_fn(&props, "initial", None) {
|
||||
signal.data.set_value(&initial_str);
|
||||
}
|
||||
|
||||
match get_string_fn(&props, "type", None) {
|
||||
Ok(signal_type) => match signal_type.to_ascii_lowercase().as_str() {
|
||||
"poll" => handle_poll(
|
||||
id.to_string(),
|
||||
&props,
|
||||
shell.clone(),
|
||||
store.clone(),
|
||||
tx.clone(),
|
||||
),
|
||||
"listen" => handle_listen(
|
||||
id.to_string(),
|
||||
&props,
|
||||
shell.clone(),
|
||||
store.clone(),
|
||||
tx.clone(),
|
||||
),
|
||||
o => log::error!("Invalid type: '{}'", o),
|
||||
},
|
||||
Err(_) => {
|
||||
log::error!(
|
||||
"Unable to handle localsignal {}: 'type' property missing or invalid.",
|
||||
id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
glib::MainContext::default().spawn_local(async move {
|
||||
while let Some(id_str) = rx.recv().await {
|
||||
let value_opt = {
|
||||
let guard = store.read().unwrap();
|
||||
guard.get(&id_str).cloned()
|
||||
};
|
||||
|
||||
if let Some(value) = value_opt {
|
||||
if let Ok(id) = id_str.parse::<u64>() {
|
||||
LOCAL_SIGNALS.with(|registry| {
|
||||
let mut registry_ref = registry.borrow_mut();
|
||||
|
||||
if let Some(signal) = registry_ref.get_mut(&id) {
|
||||
let original = value.to_string();
|
||||
let mut current = original.clone();
|
||||
|
||||
let mutations: Vec<rhai::FnPtr> = match signal.props.get("mutations") {
|
||||
Some(v) => {
|
||||
if let Ok(arr) = v.as_array_ref() {
|
||||
arr.iter()
|
||||
.filter_map(|item| {
|
||||
item.clone().try_cast::<rhai::FnPtr>().or_else(|| {
|
||||
log::warn!("Non-function found in signal.props.mutations");
|
||||
None
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
log::warn!("Localsignal mutations property is not an array");
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
None => Vec::new(),
|
||||
};
|
||||
|
||||
if mutations.is_empty() {
|
||||
signal.data.set_value(¤t);
|
||||
return;
|
||||
}
|
||||
|
||||
let parser_rc = parser.borrow_mut();
|
||||
let compiled_ast = match ast.as_ref() {
|
||||
Some(rc) => rc.borrow(),
|
||||
None => {
|
||||
log::warn!("No compiled AST available");
|
||||
signal.data.set_value(¤t);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for mutation in mutations {
|
||||
match mutation.call::<String>(&parser_rc.engine, &compiled_ast, (current.clone(),)) {
|
||||
Ok(v) => {
|
||||
current = v;
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
log::warn!(
|
||||
"Signal {} mutation failed ({}), reverting to original value",
|
||||
id,
|
||||
e
|
||||
);
|
||||
current = original.clone();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signal.data.set_value(¤t);
|
||||
} else {
|
||||
log::warn!("No LocalSignal found for id {}", id);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log::error!("Invalid id_str '{}': cannot parse to u64", id_str);
|
||||
}
|
||||
} else {
|
||||
log::warn!("No value found in store for id '{}'", id_str);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -15,12 +15,16 @@
|
||||
*/
|
||||
|
||||
mod listen;
|
||||
mod localsignal;
|
||||
mod poll;
|
||||
|
||||
pub use localsignal::*;
|
||||
|
||||
use crate::ast::WidgetNode;
|
||||
use listen::handle_listen;
|
||||
use once_cell::sync::Lazy;
|
||||
use poll::handle_poll;
|
||||
use std::process::Command;
|
||||
use std::sync::Mutex;
|
||||
use std::{collections::HashMap, sync::Arc, sync::RwLock};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
@@ -30,22 +34,30 @@ pub type ReactiveVarStore = Arc<RwLock<HashMap<String, String>>>;
|
||||
pub static SHUTDOWN_REGISTRY: Lazy<Mutex<Vec<watch::Sender<bool>>>> =
|
||||
Lazy::new(|| Mutex::new(Vec::new()));
|
||||
|
||||
pub fn get_prefered_shell() -> String {
|
||||
// Check Dash and prefer if dash is installed.
|
||||
let dash_installed: bool =
|
||||
Command::new("which").arg("dash").output().map(|o| o.status.success()).unwrap_or(false);
|
||||
|
||||
let shell = if dash_installed { String::from("/bin/dash") } else { String::from("/bin/sh") };
|
||||
|
||||
shell
|
||||
}
|
||||
|
||||
pub fn handle_state_changes(
|
||||
root_node: &WidgetNode,
|
||||
tx: UnboundedSender<String>,
|
||||
) -> ReactiveVarStore {
|
||||
// Enter node is the WidgetNode of Enter()
|
||||
// it is the very root of every config.
|
||||
let store: ReactiveVarStore = Arc::new(RwLock::new(HashMap::new()));
|
||||
|
||||
store: ReactiveVarStore,
|
||||
) {
|
||||
let shell = get_prefered_shell();
|
||||
if let WidgetNode::Enter(children) = root_node {
|
||||
for child in children {
|
||||
match child {
|
||||
WidgetNode::Poll { var, props } => {
|
||||
handle_poll(var.to_string(), props, store.clone(), tx.clone());
|
||||
handle_poll(var.to_string(), props, shell.clone(), store.clone(), tx.clone());
|
||||
}
|
||||
WidgetNode::Listen { var, props } => {
|
||||
handle_listen(var.to_string(), props, store.clone(), tx.clone());
|
||||
handle_listen(var.to_string(), props, shell.clone(), store.clone(), tx.clone());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -53,8 +65,6 @@ pub fn handle_state_changes(
|
||||
} else {
|
||||
log::warn!("Expected Enter() as root node for config");
|
||||
}
|
||||
|
||||
store
|
||||
}
|
||||
|
||||
pub fn kill_state_change_handler() {
|
||||
|
||||
@@ -26,6 +26,7 @@ use tokio::time::sleep;
|
||||
pub fn handle_poll(
|
||||
var_name: String,
|
||||
props: &Map,
|
||||
shell: String,
|
||||
store: ReactiveVarStore,
|
||||
tx: tokio::sync::mpsc::UnboundedSender<String>,
|
||||
) {
|
||||
@@ -57,7 +58,7 @@ pub fn handle_poll(
|
||||
|
||||
tokio::spawn(async move {
|
||||
// Spawn a persistent shell
|
||||
let mut child = match Command::new("/bin/sh")
|
||||
let mut child = match Command::new(&shell)
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
|
||||
@@ -4,14 +4,12 @@ version = "0.1.0"
|
||||
authors = ["byson94 <byson94wastaken@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
description = "Utility crate used in eww"
|
||||
repository = "https://github.com/elkowar/eww"
|
||||
homepage = "https://github.com/elkowar/eww"
|
||||
description = "Utility crate used in ewwii"
|
||||
repository = "https://github.com/ewwii-sh/ewwii"
|
||||
homepage = "https://github.com/ewwii-sh/ewwii"
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
derive_more.workspace = true
|
||||
ref-cast.workspace = true
|
||||
chrono = { workspace = true, features = ["unstable-locales"] }
|
||||
rhai = { version = "1.22.2" }
|
||||
anyhow.workspace = true
|
||||
rhai = { workspace = true, features = ["internals"] }
|
||||
anyhow.workspace = true
|
||||
once_cell.workspace = true
|
||||
@@ -63,6 +63,12 @@ pub fn get_i32_prop(props: &Map, key: &str, default: Option<i32>) -> Result<i32>
|
||||
if let Some(value) = props.get(key) {
|
||||
if let Some(v) = value.clone().try_cast::<i32>() {
|
||||
Ok(v)
|
||||
} else if let Some(v) = value.clone().try_cast::<i64>() {
|
||||
if v >= i32::MIN as i64 && v <= i32::MAX as i64 {
|
||||
Ok(v as i32)
|
||||
} else {
|
||||
Err(anyhow!("Value for `{}` is out of range for i32", key))
|
||||
}
|
||||
} else if let Some(s) = value.clone().try_cast::<String>() {
|
||||
s.parse::<i32>()
|
||||
.map_err(|_| anyhow!("Expected property `{}` to be an i32 or numeric string", key))
|
||||
@@ -113,12 +119,18 @@ pub fn get_duration_prop(props: &Map, key: &str, default: Option<Duration>) -> R
|
||||
let mins =
|
||||
num.parse::<u64>().map_err(|_| anyhow!("Invalid min value: '{}'", key_str))?;
|
||||
Ok(Duration::from_secs(mins * 60))
|
||||
} else if key_str.ends_with("m") {
|
||||
let num = &key_str[..key_str.len() - 1];
|
||||
let mins = num.parse::<u64>().map_err(|_| anyhow!("Invalid m value: '{}'", key_str))?;
|
||||
Ok(Duration::from_secs(mins * 60))
|
||||
} else if key_str.ends_with("h") {
|
||||
let num = &key_str[..key_str.len() - 1];
|
||||
let hrs = num.parse::<u64>().map_err(|_| anyhow!("Invalid h value: '{}'", key_str))?;
|
||||
Ok(Duration::from_secs(hrs * 3600))
|
||||
} else {
|
||||
Err(anyhow!("Unsupported duration format: '{}'", key_str))
|
||||
default.ok_or_else(|| {
|
||||
anyhow!("Unsupported duration format: '{}', and no default provided", key_str)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
default.ok_or_else(|| anyhow!("No value for duration and no default provided"))
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
pub mod extract_props;
|
||||
pub mod locale;
|
||||
pub mod span;
|
||||
pub mod wrappers;
|
||||
|
||||
pub use locale::*;
|
||||
pub use span::*;
|
||||
pub use wrappers::*;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
use chrono::Locale;
|
||||
use std::env::var;
|
||||
|
||||
/// Returns the `Locale` enum based on the `LC_ALL`, `LC_TIME`, and `LANG` environment variables in
|
||||
/// that order, which is the precedence order prescribed by Section 8.2 of POSIX.1-2017.
|
||||
/// If the environment variable is not defined or is malformed use the POSIX locale.
|
||||
pub fn get_locale() -> Locale {
|
||||
var("LC_ALL").or_else(|_| var("LC_TIME")).or_else(|_| var("LANG")).map_or(Locale::POSIX, |v| {
|
||||
v.split('.').next().and_then(|x| x.try_into().ok()).unwrap_or_default()
|
||||
})
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
use derive_more::{Debug, *};
|
||||
use ref_cast::RefCast;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The name of a variable
|
||||
#[repr(transparent)]
|
||||
#[derive(
|
||||
Clone,
|
||||
Hash,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
AsRef,
|
||||
From,
|
||||
FromStr,
|
||||
Display,
|
||||
Debug,
|
||||
RefCast,
|
||||
)]
|
||||
#[debug("VarName({})", _0)]
|
||||
pub struct VarName(pub String);
|
||||
|
||||
impl std::borrow::Borrow<str> for VarName {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AttrName {
|
||||
pub fn to_attr_name_ref(&self) -> &AttrName {
|
||||
AttrName::ref_cast(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for VarName {
|
||||
fn from(s: &str) -> Self {
|
||||
VarName(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AttrName> for VarName {
|
||||
fn from(x: AttrName) -> Self {
|
||||
VarName(x.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// The name of an attribute
|
||||
#[repr(transparent)]
|
||||
#[derive(
|
||||
Clone,
|
||||
Hash,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
AsRef,
|
||||
From,
|
||||
FromStr,
|
||||
Display,
|
||||
Debug,
|
||||
RefCast,
|
||||
)]
|
||||
#[debug("AttrName({})", _0)]
|
||||
pub struct AttrName(pub String);
|
||||
|
||||
impl AttrName {
|
||||
pub fn to_var_name_ref(&self) -> &VarName {
|
||||
VarName::ref_cast(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::borrow::Borrow<str> for AttrName {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for AttrName {
|
||||
fn from(s: &str) -> Self {
|
||||
AttrName(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VarName> for AttrName {
|
||||
fn from(x: VarName) -> Self {
|
||||
AttrName(x.0)
|
||||
}
|
||||
}
|
||||
1
docs/.gitignore
vendored
1
docs/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
book/
|
||||
@@ -1,3 +0,0 @@
|
||||
# Ewwii docs
|
||||
|
||||
Ewwii documentation page.
|
||||
@@ -1,14 +0,0 @@
|
||||
[book]
|
||||
authors = ["byson94"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "Ewwii documentation"
|
||||
|
||||
[output.html]
|
||||
default-theme = "latte"
|
||||
preferred-dark-theme = "ewwii"
|
||||
site-url = "/ewwii/"
|
||||
git-repository-url = "https://github.com/Ewwii-sh/ewwii"
|
||||
additional-js = ["./js/home_button.js", "./theme/rhai-autodocs/tabs.js"]
|
||||
additional-css = ["./theme/catppuccin.css", "./theme/ewwii.css", "./theme/rhai-autodocs/default.css"]
|
||||
@@ -1,31 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const homeBtn = document.createElement("a");
|
||||
homeBtn.href = "https://ewwii-sh.github.io/";
|
||||
homeBtn.innerText = "🏠︎";
|
||||
homeBtn.className = "home-button";
|
||||
|
||||
homeBtn.style.padding = "6px";
|
||||
homeBtn.style.backgroundColor = "transparent";
|
||||
homeBtn.style.color = "grey";
|
||||
homeBtn.style.borderRadius = "4px";
|
||||
homeBtn.style.textDecoration = "none";
|
||||
homeBtn.style.display = "inline-flex";
|
||||
homeBtn.style.alignItems = "center";
|
||||
homeBtn.style.justifyContent = "center";
|
||||
homeBtn.style.transition = "all 0.2s ease";
|
||||
|
||||
// Hover effect
|
||||
homeBtn.addEventListener("mouseover", () => {
|
||||
homeBtn.style.color = "#bbbbbb";
|
||||
homeBtn.style.transform = "scale(1.01)";
|
||||
});
|
||||
|
||||
homeBtn.addEventListener("mouseout", () => {
|
||||
homeBtn.style.backgroundColor = "transparent";
|
||||
homeBtn.style.color = "grey";
|
||||
homeBtn.style.transform = "scale(1)";
|
||||
});
|
||||
|
||||
const header = document.querySelector(".right-buttons");
|
||||
if (header) header.appendChild(homeBtn);
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
# Summary
|
||||
|
||||
[Introduction](introduction.md)
|
||||
|
||||
- [Getting Started](getting_started.md)
|
||||
|
||||
- [Installation](installation.md)
|
||||
|
||||
- [Configuration & Syntax](config/config_and_syntax.md)
|
||||
|
||||
- [Configuration](config/configuration.md)
|
||||
- [Fundamentals](config/config_fundamentals.md)
|
||||
- [Variables](config/variables.md)
|
||||
- [Expression Language](config/expression_language.md)
|
||||
- [Rendering and Best Practices](config/rendering_and_best_practices.md)
|
||||
|
||||
- [Widgets](widgets/widgets.md)
|
||||
|
||||
- [Widgets & Parameters](widgets/widgets_and_params.md)
|
||||
- [Widget Properties](widgets/props.md)
|
||||
|
||||
- [Theming & UI](theming/theming_and_ui.md)
|
||||
|
||||
- [Working With GTK](theming/working_with_gtk.md)
|
||||
- [Styling Widgets](theming/styling_widgets.md)
|
||||
|
||||
- [Modules](modules/modules.md)
|
||||
|
||||
- [User Defined Modules](modules/user_defined.md)
|
||||
- [Global Builtin Rhai Functions](modules/global.md)
|
||||
- [Std Library](modules/stdlib.md)
|
||||
- [API Library](modules/apilib.md)
|
||||
|
||||
- [Examples](examples/examples.md)
|
||||
|
||||
- [Starter Bar](examples/starter_bar.md)
|
||||
- [Wifi Manager Template](examples/wifi_manager.md)
|
||||
|
||||
- [Advanced Commands](commands/advanced_commands_intro.md)
|
||||
|
||||
- [Update](commands/update.md)
|
||||
- [Call Functions](commands/call_fns.md)
|
||||
|
||||
- [Troubleshooting](troubleshooting.md)
|
||||
@@ -1,9 +0,0 @@
|
||||
# Advanced Commands
|
||||
|
||||
Now that you all are familiar with ewwii and may be making awesome widgets, lets get to know about some advanced commands in ewwii, that makes it even more flexible and powerful.
|
||||
|
||||
In this section, we will cover:
|
||||
|
||||
- How to use advanced commands
|
||||
- Limitations of these advanced commands
|
||||
- And finally, solutions to these limitations
|
||||
@@ -1,44 +0,0 @@
|
||||
# `ewwii call-fns`
|
||||
|
||||
The `call-fns` command allows you to call **Rhai functions** directly from the command line. This can be useful for triggering specific functionality in your widget configuration without updating or interacting with the GUI.
|
||||
|
||||
> **Note:** All variables created by `poll` or `listen` handlers will default to their initial values when calling functions. They **cannot be preserved**.
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
# Call a single Rhai function
|
||||
ewwii call-fns "my_function(32, 21)"
|
||||
|
||||
# Call multiple functions
|
||||
ewwii call-fns "first_function('string')" "second_function()"
|
||||
```
|
||||
|
||||
This will execute the specified functions in the context of your current configuration.
|
||||
|
||||
## Limitation: Main configuration
|
||||
|
||||
However, there is one limitation in `call-fns`. It can only see and call functions defined within your main configuration (i.e `ewwii.rhai` file).
|
||||
|
||||
## Solution: Creating wrappers
|
||||
|
||||
But, there is one solution though! You can call external functions from functions defined in main.
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
// in external.rhai
|
||||
fn awesome_fn() {
|
||||
print("Awesome fn triggered!");
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// in ewwii.rhai
|
||||
fn call_awesome_fn() {
|
||||
import "external" as external;
|
||||
external::awesome_fn();
|
||||
}
|
||||
```
|
||||
|
||||
Now you can run `ewwii call-fns "call-awesome_fn()"` and get "Awesome fn triggered!".
|
||||
@@ -1,37 +0,0 @@
|
||||
# `ewwii update`
|
||||
|
||||
The `update` command refreshes the widgets rendered in a specified window. It allows you to immediately reflect changes in your widget configuration.
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
# Update a single window
|
||||
ewwii update --window "my_awesome_widget"
|
||||
|
||||
# Short form
|
||||
ewwii update -w "my_awesome_widget"
|
||||
```
|
||||
|
||||
## Limitation: `poll` and `listen` handlers
|
||||
|
||||
A known limitation of `ewwii update` is that it does **not** preserve variables created by `poll` or `listen` handlers. When you run `ewwii update`, these variables are reset to their initial values.
|
||||
|
||||
## Solution: Injecting variables with `--inject-vars`
|
||||
|
||||
To prevent certain variables from being reset, you can manually inject values into the configuration using the `--inject-vars` argument. This allows you to explicitly set variable values during an update.
|
||||
|
||||
```bash
|
||||
# Long form
|
||||
ewwii update --window "my_awesome_widget" --inject-vars "VAR1=bar,VAR2=foo2"
|
||||
|
||||
# Short form
|
||||
ewwii update -w "my_awesome_widget" --inject-vars "VAR1=ewwii,VAR2=baz1"
|
||||
```
|
||||
|
||||
> Note: `--inject-vars` does **not** automatically capture the current state of `poll` or `listen` variables. You must explicitly provide the values you want.
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Consider using `--inject-vars` when your widgets rely on `poll` or `listen` to avoid default resets.
|
||||
- Update multiple widgets by running `ewwii update` for each window separately.
|
||||
- Be explicit and consistent with variable values to avoid unexpected behavior.
|
||||
@@ -1,14 +0,0 @@
|
||||
# Configuration & Syntax
|
||||
|
||||
This section introduces the foundational systems that define how you configure your UI, express logic, and work with dynamic data in Rahi (Ewwii's configuration language).
|
||||
|
||||
The configuration model is imparative by nature but will be used in a declarative format that is made to make configuring ewwii easy as well as to provide a logical way of configuring. You'll also work with special built in functions and modules that expose live system state and other information.
|
||||
|
||||
We'll cover:
|
||||
|
||||
- The structure of configuration files
|
||||
- Embedding expressions within properties and attributes
|
||||
- Accessing and using built-in variables
|
||||
- Interpolation, scoping, and reactivity rules
|
||||
|
||||
> If you're coming from EWW's Yuck, expect similarities in structure but with much more flexibility and logical programming.
|
||||
@@ -1,119 +0,0 @@
|
||||
# Fundamentals
|
||||
|
||||
Ewwii uses Rhai as its configuration language. But instead of just pure Rhai, ewwii has its own layout that users should follow to create widgets using Rhai. And you may be wondering why ewwii has a "custom" layout instead of allowing users to just use pure Rhai. It's good question and the reason why ewwii has a custom layout is because it tries to remove unnecessary complexity.
|
||||
|
||||
The full reasons for this layout wont be explained much more because it goes way deeper than just "decreasing complexity".
|
||||
|
||||
For more information about Rhai, you can read [their documentation](https://rhai.rs/book/).
|
||||
|
||||
## Widgets and their parameters
|
||||
|
||||
Each widget in ewwii is a function (e.g: `button(#{...})` is a function call to create a button). So each one requires its own parameters.
|
||||
|
||||
For example, `defwindow` expects a **String**, **Properties**, and a function call that **returns a widget.**
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
enter([
|
||||
// the string here (the text in "") is the name of the window
|
||||
// the content in #{} is the properties
|
||||
// and the 3rd `root_widget()` call is the function that returns a child.
|
||||
|
||||
// defwindow cant have children in [] directly, but it expects a function returning it for it.
|
||||
defwindow("example", #{
|
||||
monitor: 0,
|
||||
windowtype: "dock",
|
||||
stacking: "fg",
|
||||
wm_ignore: false,
|
||||
geometry: #{
|
||||
x: "0%",
|
||||
y: "2px",
|
||||
width: "90%",
|
||||
height: "30px",
|
||||
anchor: "top center"
|
||||
},
|
||||
reserve: #{ distance: "40px" side: "top" }
|
||||
}, root_widget())
|
||||
])
|
||||
```
|
||||
|
||||
This is not that complex once you know the parameters of defwindow as most of the other widgets only take in properties or optinally children. **Poll/Listen** are the only things that is similar to `defwindow` and you will learn about it later in the [variables chapter](./variables.md).
|
||||
|
||||
## The root
|
||||
|
||||
It is an important concept that users must know to go forward with this documentaiton. Ewwii's Rhai layout is made to be **logical and powerful**, so the user is given access the root of the entire widget system.
|
||||
|
||||
The root is defined as `enter()` and it is where you should write `defwindow`.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```js
|
||||
enter([
|
||||
defwindow("example", #{
|
||||
monitor: 0,
|
||||
windowtype: "dock",
|
||||
stacking: "fg",
|
||||
wm_ignore: false,
|
||||
geometry: #{
|
||||
x: "0%",
|
||||
y: "2px",
|
||||
width: "90%",
|
||||
height: "30px",
|
||||
anchor: "top center"
|
||||
},
|
||||
reserve: #{ distance: "40px" side: "top" }
|
||||
}, root_widget())
|
||||
])
|
||||
```
|
||||
|
||||
Now that you saw this example, you may be wondering why we are doing `enter([])` instead of `enter()`. That is due to another fundamental concept in ewwii which is very important. You will learn about it in the [properties and child definition section](#properties-and-child-definition).
|
||||
|
||||
## Semi-colons
|
||||
|
||||
Semi-colon is an important character in Rhai. Just like programming languages like JavaScript, Java, Rust etc.
|
||||
|
||||
You can use the following link to read about semi-colons in the Rhai book as they have an awesome documentation.
|
||||
|
||||
[https://rhai.rs/book/ref/statements.html#statements](https://rhai.rs/book/ref/statements.html#statements)
|
||||
|
||||
## Properties and child definition
|
||||
|
||||
The part where most people get confused is the use of `[]` and `#{}`. Let's get into what those are and how you can use them.
|
||||
|
||||
The `[]` is used for adding **children** to a widget.
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
fn greeter(foo) {
|
||||
return box(#{
|
||||
orientation: "horizontal",
|
||||
halign: "center"
|
||||
}, [
|
||||
// the `[]` holds a button which is the child widget of the box widget
|
||||
// each element in a `[]` should end in a comma (,) instead of a semi-colon (;).
|
||||
button(#{ onclick: `notify-send '${foo}'`, label: "baz" }),
|
||||
label(#{ text: "example" }),
|
||||
]);
|
||||
};
|
||||
```
|
||||
|
||||
The `#{}` works similar to the `[]` but, it is used to add properties into the widget.
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
fn greeter(foo) {
|
||||
// the `#{}` holds the properties of the box widget
|
||||
// each element in a `#{}` should end in a comma (,) instead of a semi-colon (;).
|
||||
return box(#{
|
||||
orientation: "horizontal",
|
||||
halign: "center"
|
||||
}, [
|
||||
// properties are assigned to both button and label using the #{}.
|
||||
button(#{ onclick: `notify-send '${foo}'`, label: "baz" }),
|
||||
label(#{ text: "example" }),
|
||||
]);
|
||||
};
|
||||
```
|
||||
@@ -1,145 +0,0 @@
|
||||
# Writing your ewwii configuration
|
||||
|
||||
(For a list of all built-in widgets (i.e. `box`, `label`, `button`), see [Widget Documentation](../widgets/widgets.md).)
|
||||
|
||||
Ewwii is configured using a language called `Rhai`.
|
||||
Using rhai, you declare the structure and content of your widgets, the geometry, position, and behavior of any windows,
|
||||
as well as any state and data that will be used in your widgets.
|
||||
Rhai is based around imparative syntax, which you may know from programming languages like C, Rust etc.
|
||||
If you're using vim, you can make use of [vim-rhai](https://github.com/rhaiscript/vim-rhai) for editor support.
|
||||
If you're using VSCode, you can get syntax highlighting and formatting from [vscode-rhai](https://marketplace.visualstudio.com/items?itemName=rhaiscript.vscode-rhai).
|
||||
|
||||
Additionally, any styles are defined in CSS or SCSS (which is mostly just slightly improved CSS syntax).
|
||||
While ewwii supports a significant portion of the CSS you know from the web,
|
||||
not everything is supported, as ewwii relies on GTK's own CSS engine.
|
||||
Notably, some animation features are unsupported,
|
||||
as well as most layout-related CSS properties such as flexbox, `float`, absolute position or `width`/`height`.
|
||||
|
||||
To get started, you'll need to create two files: `ewwii.rhai` and `ewwii.scss` (or `ewwii.css`, if you prefer).
|
||||
These files must be placed under `$XDG_CONFIG_HOME/ewwii` (this is most likely `~/.config/ewwii`).
|
||||
|
||||
Now that those files are created, you can start writing your first widget!
|
||||
|
||||
## Creating your first window
|
||||
|
||||
Firstly, you will need to create a top-level window. Here, you configure things such as the name, position, geometry, and content of your window.
|
||||
|
||||
Let's look at an example window definition:
|
||||
|
||||
```js
|
||||
enter([ // Add all defwindow inside enter. Enter is the root of the config.
|
||||
defwindow("example", #{
|
||||
monitor: 0,
|
||||
windowtype: "dock",
|
||||
stacking: "fg",
|
||||
wm_ignore: false,
|
||||
geometry: #{
|
||||
x: "0%",
|
||||
y: "2px",
|
||||
width: "90%",
|
||||
height: "30px",
|
||||
anchor: "top center"
|
||||
},
|
||||
reserve: #{ distance: "40px" side: "top" }
|
||||
}, label(#{ text: "example content" }))
|
||||
])
|
||||
```
|
||||
|
||||
Here, we are defining a window named `example`, which we then define a set of properties for. Additionally, we set the content of the window to be the text `"example content"`.
|
||||
|
||||
You can now open your first window by running `ewwii open example`! Glorious!
|
||||
|
||||
### `defwindow`-properties
|
||||
|
||||
| Property | Description |
|
||||
| ---------: | ------------------------------------------------------------------------ |
|
||||
| `monitor` | Which monitor this window should be displayed on. See below for details. |
|
||||
| `geometry` | Geometry of the window. |
|
||||
|
||||
**`monitor`-property**
|
||||
|
||||
This field can be:
|
||||
|
||||
- the string `<primary>`, in which case ewwii tries to identify the primary display (which may fail, especially on wayland)
|
||||
- an integer, declaring the monitor index
|
||||
- the name of the monitor
|
||||
- a string containing a JSON-array of monitor matchers, such as: `'["<primary>", "HDMI-A-1", "PHL 345B1C", 0]'`. Ewwii will try to find a match in order, allowing you to specify fallbacks.
|
||||
|
||||
**`geometry`-properties**
|
||||
|
||||
| Property | Description |
|
||||
| ----------------: | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| `x`, `y` | Position of the window. Values may be provided in `px` or `%`. Will be relative to `anchor`. |
|
||||
| `width`, `height` | Width and height of the window. Values may be provided in `px` or `%`. |
|
||||
| `anchor` | Anchor-point of the window. Either `center` or combinations of `top`, `center`, `bottom` and `left`, `center`, `right`. |
|
||||
| `resizable` | Whether to allow resizing the window or not. Eiither `true` or `false`. |
|
||||
|
||||
<br/>
|
||||
Depending on if you are using X11 or Wayland, some additional properties exist:
|
||||
|
||||
#### X11
|
||||
|
||||
| Property | Description |
|
||||
| -----------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `stacking` | Where the window should appear in the stack. Possible values: `fg`, `bg`. |
|
||||
| `wm_ignore` | Whether the window manager should ignore this window. This is useful for dashboard-style widgets that don't need to interact with other windows at all. Note that this makes some of the other properties not have any effect. Either `true` or `false`. |
|
||||
| `reserve` | Specify how the window manager should make space for your window. This is useful for bars, which should not overlap any other windows. |
|
||||
| `windowtype` | Specify what type of window this is. This will be used by your window manager to determine how it should handle your window. Possible values: `normal`, `dock`, `toolbar`, `dialog`, `desktop`. Default: `dock` if `reserve` is specified, `normal` otherwise. |
|
||||
|
||||
#### Wayland
|
||||
|
||||
| Property | Description |
|
||||
| ----------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `stacking` | Where the window should appear in the stack. Possible values: `fg`, `bg`, `overlay`, `bottom`. |
|
||||
| `exclusive` | Whether the compositor should reserve space for the window automatically. Either `true` or `false`. If `true` `:anchor` has to include `center`. |
|
||||
| `focusable` | Whether the window should be able to be focused. This is necessary for any widgets that use the keyboard to work. Possible values: `none`, `exclusive` and `ondemand`. |
|
||||
| `namespace` | Set the wayland layersurface namespace ewwii uses. Accepts a `string` value. |
|
||||
|
||||
## Your first widget
|
||||
|
||||
While our bar is already looking great, it's a bit boring. Thus, let's add some actual content!
|
||||
|
||||
```js
|
||||
fn greeter(name) {
|
||||
return box(#{
|
||||
orientation: "horizontal",
|
||||
halign: "center"
|
||||
}, [
|
||||
button(#{ onclick: `notify-send 'Hello' 'Hello, ${name}'`, label: "Greet" })
|
||||
]);
|
||||
};
|
||||
```
|
||||
|
||||
To show this, let's replace the text in our window definition with a call to this new widget:
|
||||
|
||||
```js
|
||||
enter([
|
||||
defwindow(
|
||||
"example",
|
||||
#{
|
||||
// ... properties omitted
|
||||
},
|
||||
greeter("Bob")
|
||||
),
|
||||
]);
|
||||
```
|
||||
|
||||
There is a lot going on here, so let's step through this.
|
||||
|
||||
We are creating a function named `greeter` and a function is equal to a component that returns a child (widget). So function has two uses: one to return a component, and the other to do a set of functions.
|
||||
And this function takes one parameters, called `name`. The `name` parameter _must_ be provided or else, you should emit it. Rhai does allow adding optional parameters, but we will talk about it later for the sake of beginners who are in-experienced with imprative programming languages.
|
||||
|
||||
Now inside the function, we declare the body of our widget that we are returning. We make use of a `box`, which we set a couple properties of.
|
||||
|
||||
We need this `box`, as a function can only ever contain a single widget - otherwise,
|
||||
ewwii would not know if it should align them vertically or horizontally, how it should space them, and so on.
|
||||
Thus, we wrap multiple children in a `box`.
|
||||
This box then contains a button.
|
||||
In that button's `onclick` property, we refer to the provided `name` using string-interpolation syntax: `` `${name}` ``. It is not possible to use a variable within a `""` or `''` just like javascript. You can learn more about it [here](https://rhai.rs/book/ref/strings-chars.html?interpolation#string-interpolation).
|
||||
|
||||
<!-- TODO -->
|
||||
<!-- In fact, there is a lot more you can do within `${...}` - more on that in the chapter about the [expression language](expression_language.md). -->
|
||||
|
||||
To then use our widget, we call the function that provides the widget with the necessary parameters passed.
|
||||
|
||||
As you may have noticed, we are using a couple predefined widgets here. These are all listed and explained in the [widgets chapter](widgets.md).
|
||||
@@ -1,87 +0,0 @@
|
||||
# The Rhai Expression Engine
|
||||
|
||||
Ewwii uses [Rhai](https://rhai.rs/) as its core expression and scripting engine. This means your configuration is more than just static values or simple substitutions—it’s **real code**, and you can use it anywhere dynamic behavior is needed.
|
||||
|
||||
Rhai expressions can:
|
||||
|
||||
- Perform logic and branching (`if`, `match`, `? :`)
|
||||
- Handle mathematical calculations and string operations
|
||||
- Access nested data from arrays, maps, or JSON
|
||||
- Run custom functions
|
||||
- Be used directly in layout definitions, widget attributes, and style rules
|
||||
|
||||
Unlike Yuck, where expressions were embedded in `{ ... }` or `${ ... }`, Rhai treats expressions as **native**. They don’t need to be wrapped in special delimiters. If you can write it in Rhai, it just works.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
let value = 12 + foo * 10;
|
||||
|
||||
box(#{
|
||||
class: "baz"
|
||||
orientation: "h",
|
||||
}, [
|
||||
button(#{
|
||||
class: button_active ? "active" : "inactive",
|
||||
on_click: "toggle_thing",
|
||||
label: `Some math: ${value}`,
|
||||
}),
|
||||
]);
|
||||
```
|
||||
|
||||
## Core Features
|
||||
|
||||
Rhai supports a wide range of expression capabilities:
|
||||
|
||||
- **Mathematics**: `+`, `-`, `*`, `/`, `%`
|
||||
- **Comparisons**: `==`, `!=`, `<`, `>`, `<=`, `>=`
|
||||
- **Boolean logic**: `&&`, `||`, `!`
|
||||
- **Conditionals**: `if/else`, ternary (`cond ? a : b`)
|
||||
- **Regex matching**: `=~` operator (Rust-style regex)
|
||||
- **Optional access**: `?.` and `?.[index]`
|
||||
- **Data structures**: maps/arrays (`obj.field`, `arr[3]`, `map["key"]`)
|
||||
- **Function calls**: standard and Ewwii-specific built-ins (see below)
|
||||
- **String interpolation**: `` `Value is ${value}` `` (standard Rhai feature)
|
||||
|
||||
> Note
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> Rhai only allows string interpolation only for the strings that are quoted in `` similar to JavaScript.
|
||||
>
|
||||
> > Learn more about it [here](https://rhai.rs/book/ref/strings-chars.html?interpolation#string-interpolation).
|
||||
|
||||
## Common Built-in Functions
|
||||
|
||||
💡 _You may recognize some of these from the old expression system—but now they're just Rhai functions._
|
||||
|
||||
Examples include:
|
||||
|
||||
- `round()`, `floor()`, `ceil()`, `powi()`, `powf()`
|
||||
- `min()`, `max()`, `sin()`, `cos()`, etc.
|
||||
- `replace()`, `matches()`, `captures()`
|
||||
- `strlength()`, `arraylength()`, `objectlength()`
|
||||
- `jq()` – run jaq-compatible filters on JSON data
|
||||
- `get_env()` – read environment variables
|
||||
- `formattime()` – format UNIX timestamps
|
||||
- `formatbytes()` – format file sizes (IEC or SI)
|
||||
|
||||
## Dynamic Usage
|
||||
|
||||
Because expressions are just Rhai, you can now write real logic inline or break it into reusable functions:
|
||||
|
||||
```js
|
||||
fn status_text(active) {
|
||||
return active ? "enabled" : "disabled";
|
||||
}
|
||||
|
||||
label({
|
||||
text: `Status: ${status_text(system_active)}`
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### TL;DR
|
||||
|
||||
> If you’ve used scripting languages like JavaScript or Lua, you’ll feel at home. Rhai gives you real control—not just interpolation tricks.
|
||||
@@ -1,133 +0,0 @@
|
||||
# Rendering and Best Practices
|
||||
|
||||
## Rendering children in your widgets
|
||||
|
||||
As your configuration grows, you might want to improve its structure by factoring out pieces into reusable functions.
|
||||
|
||||
In Ewwii’s Rhai-based configuration system, you can define wrapper functions that return widgets and accept a `children` parameter (or any parameter that you prefer), just like built-in widgets such as `box()` or `button()`.
|
||||
|
||||
Here's an example of a custom container that adds a label before its children:
|
||||
|
||||
```js
|
||||
fn labeled_container(name, children = []) {
|
||||
return box(#{ class: "container" }, [label(#{text: name})] + children)
|
||||
}
|
||||
```
|
||||
|
||||
You can call it like this:
|
||||
|
||||
```js
|
||||
labeled_container("foo", [
|
||||
button(#{ onclick: "notify-send hey ho", label: "Click me" }),
|
||||
]);
|
||||
```
|
||||
|
||||
Because children are just a list of widgets, you can also write functions that structure them however you'd like. For example, here's a layout that places the first two children side by side:
|
||||
|
||||
```js
|
||||
fn two_boxes(children = []) {
|
||||
return box(#{}, [
|
||||
box(#{ class: "first" }, [children[0]]),
|
||||
box(#{ class: "second" }, [children[1]])
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
And call it like this:
|
||||
|
||||
```js
|
||||
two_boxes([label(#{ text: "First" }), label(#{ text: "Second" })]);
|
||||
```
|
||||
|
||||
If a child is missing (e.g., children[1] doesn't exist), make sure to handle that gracefully or document the expected number of children.
|
||||
|
||||
<!-- TODO: add it once literal is implemented -->
|
||||
<!-- ## Dynamically generated widgets with `literal`
|
||||
|
||||
In some cases, you want to not only change the text,
|
||||
value, or color of a widget dynamically, but instead want to generate an entire widget structure dynamically.
|
||||
This is necessary if you want to display lists of things (for example notifications)
|
||||
where the amount is not necessarily known,
|
||||
or if you want to change the widget structure in some other, more complex way.
|
||||
|
||||
For this, you can make use of one of ewwii's most powerful features: the `literal` widget.
|
||||
|
||||
```js
|
||||
let variable_containing_rhai = "(box (button 'foo') (button 'bar'))";
|
||||
|
||||
// Then, inside your widget, use:
|
||||
literal(#{ content: variable_containing_rhai })
|
||||
```
|
||||
|
||||
Here, you specify the content of your literal by providing it a string (most likely stored in a variable) which contains a single yuck widget tree.
|
||||
Ewwii then reads the provided value and renders the resulting widget. Whenever it changes, the widget will be rerendered.
|
||||
|
||||
Note that this is not all that efficient. Make sure to only use `literal` when necessary! -->
|
||||
|
||||
## Window ID
|
||||
|
||||
In some cases you may want to use the same window configuration for multiple widgets, e.g. for multiple windows. This is where arguments and ids come in.
|
||||
|
||||
Firstly let us start off with ids. An id can be specified in the `open` command
|
||||
with `--id`, by default the id will be set to the name of the window
|
||||
configuration. These ids allow you to spawn multiple of the same windows. So
|
||||
for example you can do:
|
||||
|
||||
```bash
|
||||
ewwii open my_bar --screen 0 --id primary
|
||||
ewwii open my_bar --screen 1 --id secondary
|
||||
```
|
||||
|
||||
## Generating a list of widgets from array using `for`
|
||||
|
||||
If you want to display a list of values, you can use the `for`-Element to fill a container with a list of elements generated from a JSON-array.
|
||||
|
||||
```js
|
||||
let my_array = [1, 2, 3];
|
||||
|
||||
// Then, inside your widget, you can use
|
||||
box(#{}, [
|
||||
for entry in my_array {
|
||||
button(#{ onclick: `notify-send 'click' 'button ${entry}'`, label: entry.to_string() })
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
This can be useful in many situations, for example when generating a workspace list from an array representation of your workspaces.
|
||||
In many cases, this can be used instead of `literal`, and should most likely be preferred in those cases.
|
||||
|
||||
<!-- To see how to declare and use more advanced data structures, check out the [data structures example](/examples/data-structures/ewwii.rhai). -->
|
||||
|
||||
## Splitting up your configuration
|
||||
|
||||
As time passes, your configuration might grow larger and larger. Luckily, you can easily split up your configuration into multiple files!
|
||||
|
||||
There are two options to achieve this:
|
||||
|
||||
### Using `import/export`
|
||||
|
||||
```js
|
||||
// in ./foo/baz.rhai
|
||||
/// Note: all functions are automatically exported.
|
||||
fn greet() { return "Greetings!" }
|
||||
let PI = 3.14159
|
||||
export PI; // we need to export variables manually
|
||||
|
||||
// in ./ewwii.rhai
|
||||
import "foo/baz" as example;
|
||||
print(example::greet()); // Greetings!
|
||||
print(example::PI); // 3.14159
|
||||
```
|
||||
|
||||
A rhai file may import the contents of any other rhai file that they export. For this, make use of the `import` directive. If you are exporting a variable/function, make use the `export` directive.
|
||||
|
||||
### Using a separate ewwii configuration directory
|
||||
|
||||
If you want to separate different widgets even further, you can create a new ewwii config folder anywhere else.
|
||||
Then, you can tell ewwii to use that configuration directory by passing _every_ command the `--config /path/to/your/config/dir` flag.
|
||||
Make sure to actually include this in all your `ewwii` calls, including `ewwii kill`, `eww logs`, etc.
|
||||
This launches a separate instance of the ewwii daemon that has separate logs and state from your main ewwii configuration.
|
||||
|
||||
```bash
|
||||
ewwii --config "/path/to/your/config/dir"
|
||||
```
|
||||
@@ -1,122 +0,0 @@
|
||||
# Variables
|
||||
|
||||
Now that you feel sufficiently greeted by your bar, you may realize that showing data like the time and date might be even more useful than having a button that greets you.
|
||||
|
||||
To implement dynamic content in your widgets, you make use of _variables_.
|
||||
|
||||
All variables are only locally available so you would need to pass it around using function parameters. And whenever the variable changes, the value in the widget will update!
|
||||
|
||||
## Static variables
|
||||
|
||||
In Rhai, all variables are dynamically typed bindings to values. You can define variables using let, pass them as function parameters.
|
||||
|
||||
**Basic variables (`let`)**
|
||||
|
||||
```js
|
||||
let foo = "value";
|
||||
```
|
||||
|
||||
This is the simplest type of variable.
|
||||
Basic variables don't ever change automatically, if you need a dynamic variable, you can use built in functions like `poll()` and `listen()` to register dynamic values which we will talk about in the following section.
|
||||
|
||||
## Dynamic variables
|
||||
|
||||
Just having static variables that wont update is pretty limiting. So, ewwii has two built in functions to register dynamic variables that can change according to the command.
|
||||
|
||||
**Polling variables (`poll`)**
|
||||
|
||||
```js
|
||||
enter([
|
||||
poll("var_name", #{
|
||||
// It is recommended to have initial property passed.
|
||||
// If not provided, it will default to no value which may cause problems when used.
|
||||
// You can pass something like "" if you want no initial value.
|
||||
initial: "inital value",
|
||||
interval: "2s",
|
||||
cmd: "date +%H:%M:%S", // command to execute
|
||||
});
|
||||
])
|
||||
```
|
||||
|
||||
A polling variable is a variable which runs a provided shell-script repeatedly, in a given interval.
|
||||
|
||||
This may be the most commonly used type of variable.
|
||||
They are useful to access any quickly retrieved value repeatedly,
|
||||
and thus are the perfect choice for showing your time, date, as well as other bits of information such as pending package updates, weather, and battery level.
|
||||
But it is important to note that these variables are locally available only in enter (a.k.a the root) and you need to pass it to other functions with something like `some_fn(foo)` when you want to use a polled variable.
|
||||
|
||||
<!-- You can also specify an initial-value. This should prevent ewwii from waiting for the result of a given command during startup, thus
|
||||
making the startup time faster. -->
|
||||
|
||||
To externally update a polling variable, `ewwii update` can be used like with basic variables to assign a value. [Learn more about ewwii update](../commands/update.md).
|
||||
|
||||
**Listening variables (`listen`)**
|
||||
|
||||
```js
|
||||
enter([
|
||||
listen("foo", #{
|
||||
initial: "whatever",
|
||||
cmd: "tail -F /tmp/some_file",
|
||||
});
|
||||
])
|
||||
```
|
||||
|
||||
Listening variables might be the most confusing of the bunch.
|
||||
A listening variable runs a script once, and reads its output continously.
|
||||
Whenever the script outputs a new line, the value will be updated to that new line.
|
||||
In the example given above, the value of `foo` will start out as `"whatever"`, and will change whenever a new line is appended to `/tmp/some_file`.
|
||||
|
||||
These are particularly useful when you want to apply changes instantaneously when an operation happens if you have a script
|
||||
that can monitor some value on its own. Volume, brightness, workspaces that get added/removed at runtime,
|
||||
monitoring currently focused desktop/tag, etc. are the most common usecases of this type of variable.
|
||||
These are particularly efficient and should be preffered if possible.
|
||||
|
||||
For example, the command `xprop -spy -root _NET_CURRENT_DESKTOP` writes the currently focused desktop whenever it changes.
|
||||
Another example usecase is monitoring the currently playing song with playerctl: `playerctl --follow metadata --format {{title}}`.
|
||||
|
||||
<!--
|
||||
**Built-in "magic" variables**
|
||||
|
||||
In addition to defining your own variables, ewwii provides some values for you to use out of the box.
|
||||
These include values such as your CPU and RAM usage.
|
||||
These mostly contain their data as JSON, which you can then get using the [json access syntax](expression_language.md).
|
||||
All available magic variables are listed [here](magic-vars.md). -->
|
||||
|
||||
<div class="warning">
|
||||
<strong>Warning:</strong> Dynamic variables created by `poll` or `listen` handlers
|
||||
should always be defined inside an <code>enter([])</code> block.
|
||||
If `poll` or `listen` is defined outside the <code>enter([])</code> block, then they simply will be ignored.
|
||||
</div>
|
||||
|
||||
## Passing variables
|
||||
|
||||
As we discussed earlier, all variables are only available locally. So, you would need to pass it around from the current scope.
|
||||
|
||||
Here is an example of how it is done:
|
||||
|
||||
```js
|
||||
let foo = "example";
|
||||
|
||||
enter([
|
||||
poll("time", #{
|
||||
initial: "inital value",
|
||||
interval: "2s",
|
||||
cmd: "date +%H:%M:%S",
|
||||
}),
|
||||
|
||||
defwindow("1", #{}, wont_work()), // wont work
|
||||
defwindow("2", #{}, will_work(time, foo)) // will work
|
||||
])
|
||||
|
||||
// Here we have 2 variables named "time" (registered dynamically by poll) and foo (a static variable)
|
||||
|
||||
// here is an example of something that wont
|
||||
fn wont_work() {
|
||||
return box(#{}, [ label(#{ text: time }), label(#{ text: foo }) ]);
|
||||
}
|
||||
|
||||
// here is an example of something that will work
|
||||
fn will_work(time, foo) { // time and foo is passed from `enter([])`
|
||||
return box(#{}, [ label(#{ text: time }), label(#{ text: foo }) ]);
|
||||
}
|
||||
```
|
||||
@@ -1,13 +0,0 @@
|
||||
# Examples
|
||||
|
||||
This section provides hands-on, practical demonstrations of everything covered so far. Real layouts, interactive components, and theming techniques will be used in these examples.
|
||||
|
||||
Each example is minimal but complete, focusing on a particular design pattern or technique. You can copy-paste them into your own config to experiment.
|
||||
|
||||
Included:
|
||||
|
||||
- Common layout patterns (e.g., bars, panels, dashboards)
|
||||
- Interactive elements like buttons, sliders, toggles
|
||||
- Theming techniques and tricks to override or enhance GTK styles
|
||||
|
||||
If you're stuck or just looking for inspiration, this is your go-to reference.
|
||||
@@ -1,15 +0,0 @@
|
||||
# Starter Bar
|
||||
|
||||

|
||||
|
||||
A basic starter bar which will be very helpful to beginners, see [examples/ewwii-bar](https://github.com/Ewwii-sh/ewwii/tree/main/examples/ewwii-bar).
|
||||
|
||||
## Installing
|
||||
|
||||
This template is regestered in the [eii-manifests](https://github.com/Ewwii-sh/eii-manifests), so you can install it via [eiipm](https://ewwii-sh.github.io/docs/package-manager/overview/): ewwii's package manager.
|
||||
|
||||
Just run the following command and have the template ready in the current working directory in an instant!
|
||||
|
||||
```bash
|
||||
eiipm i starter_template
|
||||
```
|
||||
@@ -1,15 +0,0 @@
|
||||
# Wifi Manager Template
|
||||
|
||||

|
||||
|
||||
An advanced ewwii template showcasing `api::wifi` module, manual `nm-cli` parsing, and advacned ewwii CLI commands. See the template at [Ewwii-sh/ewifi_gui_template](https://github.com/Ewwii-sh/ewifi_gui_template).
|
||||
|
||||
## Installing
|
||||
|
||||
This template is regestered in the [eii-manifests](https://github.com/Ewwii-sh/eii-manifests), so you can install it via [eiipm](https://ewwii-sh.github.io/docs/package-manager/overview/): ewwii's package manager.
|
||||
|
||||
Just run the following command and have the template ready in the current working directory!
|
||||
|
||||
```bash
|
||||
eiipm i ewifi_gui_template
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
# Getting Started
|
||||
|
||||
Getting starting with Ewwii. This section will cover how you can install and use ewwii.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.1 KiB |
@@ -1,90 +0,0 @@
|
||||
# Installation
|
||||
|
||||
The first step of using Ewwii is installing it. You would need to have the following prerequesties installed on your system to build/install ewwii.
|
||||
|
||||
**Prerequesties:**
|
||||
|
||||
- rustc
|
||||
- cargo
|
||||
|
||||
Rather than with your system package manager,
|
||||
I **strongly** recommend installing it using [rustup](https://rustup.rs/).
|
||||
|
||||
Additionally, eww requires some dynamic libraries to be available on your system.
|
||||
The exact names of the packages that provide these may differ depending on your distribution.
|
||||
The following list of package names should work for arch linux:
|
||||
|
||||
<details>
|
||||
<summary><strong>Packages (click here)</strong></summary>
|
||||
|
||||
- gtk3 (libgdk-3, libgtk-3)
|
||||
- gtk-layer-shell (only on Wayland)
|
||||
- pango (libpango)
|
||||
- gdk-pixbuf2 (libgdk_pixbuf-2)
|
||||
- libdbusmenu-gtk3
|
||||
- cairo (libcairo, libcairo-gobject)
|
||||
- glib2 (libgio, libglib-2, libgobject-2)
|
||||
- gcc-libs (libgcc)
|
||||
- glibc
|
||||
|
||||
</details>
|
||||
|
||||
> **Note** that you will most likely need the -devel variants of your distro's packages to be able to compile ewwii.
|
||||
|
||||
## Building
|
||||
|
||||
Once you have the prerequisites ready, you're ready to install and build ewwii.
|
||||
|
||||
First clone the repo:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Ewwii-sh/ewwii
|
||||
```
|
||||
|
||||
```bash
|
||||
cd ewwii
|
||||
```
|
||||
|
||||
Then build:
|
||||
|
||||
```bash
|
||||
cargo build --release --no-default-features --features x11
|
||||
```
|
||||
|
||||
**NOTE:**
|
||||
When you're on Wayland, build with:
|
||||
|
||||
```bash
|
||||
cargo build --release --no-default-features --features=wayland
|
||||
```
|
||||
|
||||
## Running ewwii
|
||||
|
||||
Once you've built it you can now run it by entering:
|
||||
|
||||
```bash
|
||||
cd target/release
|
||||
```
|
||||
|
||||
Then make the Eww binary executable:
|
||||
|
||||
```bash
|
||||
chmod +x ./ewwii
|
||||
```
|
||||
|
||||
Then to run it, enter:
|
||||
|
||||
```bash
|
||||
./ewwii daemon
|
||||
./ewwii open <window_name>
|
||||
```
|
||||
|
||||
## Installing via package managers
|
||||
|
||||
If you don't want to go through the _very_ tedious task of cloning and building ewwii, you can install it using Cargo (Rust crate manager).
|
||||
|
||||
You can run the following command to install ewwii from cargo:
|
||||
|
||||
```bash
|
||||
cargo install --git https://github.com/Ewwii-sh/ewwii
|
||||
```
|
||||
@@ -1,26 +0,0 @@
|
||||
# Ewwii - Widgets for everyone made better!
|
||||
|
||||
Ewwii (ElKowar's Wacky Widgets improved interface) is a foork of
|
||||
Eww (ElKowar's Wacky Widgets) which is a
|
||||
widget system made in [Rust](https://www.rust-lang.org/),
|
||||
which lets you create your own widgets similarly to how you can in AwesomeWM.
|
||||
|
||||
**Strength of Ewwii over Eww:**
|
||||
|
||||
- Full-fledged scripting & expressions
|
||||
- User-defined widget trees & composition
|
||||
- Built-in configuration libraries
|
||||
- Builtin tooling for better developer experience
|
||||
- Full control over reactive updates (user defines if widget should update dynamically)
|
||||
|
||||
Ewwii is configured in [Rhai](https://rhai.rs/)
|
||||
and themed using [CSS](https://en.wikipedia.org/wiki/CSS)
|
||||
or [SCSS](<https://en.wikipedia.org/wiki/Sass_(style_sheet_language)>),
|
||||
it is easy to customize and is powerful and dynamic.
|
||||
The main goal of Ewwii is to make configuration easy
|
||||
and to give the user all the power that they need.
|
||||
|
||||
Rhai is not just a basic markup language. It is a full embeddable scripting language!
|
||||
This makes ewwii's configuration even more flexible and powerful.
|
||||
|
||||
Whether you're building a tiling-friendly status bar, a floating dashboard, or a themed control panel, Ewwii gives you the tools to design it, script it, and make it your own.
|
||||
@@ -1,6 +0,0 @@
|
||||
# Magic variables
|
||||
|
||||
These are variables that are always there, without you having to import them.
|
||||
|
||||
The delay between all the updating variables except `EWW_TIME` is 2s, for `EWW_TIME` it is 1s.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# API Library
|
||||
@@ -1 +0,0 @@
|
||||
# Global Builtin Rhai Functions
|
||||
@@ -1,12 +0,0 @@
|
||||
# Modules
|
||||
|
||||
Modules undoubtedly are one of the most powerful features in Rhai. They provide infinite extensibility to ewwii.
|
||||
|
||||
Every module follows the syntax:
|
||||
|
||||
```js
|
||||
import "std::env" as env;
|
||||
let home = env::get_home_dir(); // returns `$HOME` env var value
|
||||
```
|
||||
|
||||
This allows you to write expressive, modular Rhai code with functions grouped logically under `std` or custom namespaces.
|
||||
@@ -1 +0,0 @@
|
||||
# Std Library
|
||||
@@ -1,55 +0,0 @@
|
||||
# User-Defined Modules
|
||||
|
||||
User-defined modules allow you to **organize your code** by splitting it into separate, reusable files. This makes large projects easier to maintain and understand.
|
||||
|
||||
## Exporting Items from a Module
|
||||
|
||||
In Rhai modules:
|
||||
|
||||
- **Functions are automatically exported** by default. You do **not** need to use `export` for functions.
|
||||
- **Variables, constants, and objects must be exported manually** using the `export` keyword.
|
||||
|
||||
```js
|
||||
// File: ./foo/baz.rhai
|
||||
|
||||
/// A function that is automatically exported
|
||||
fn greet() {
|
||||
return "Greetings!"
|
||||
}
|
||||
|
||||
/// A private function, NOT exported automatically
|
||||
private fn foo() {
|
||||
return "This function is hidden and not exported"
|
||||
}
|
||||
|
||||
/// A variable
|
||||
let PI = 3.14159;
|
||||
|
||||
// Export the variable explicitly
|
||||
export PI;
|
||||
```
|
||||
|
||||
**Tip:** Only variables, constants, and objects require the `export` keyword. Functions are always available unless marked `private`. [More info](https://rhai.rs/book/language/modules/export.html#export-functions)
|
||||
|
||||
## Importing a Module
|
||||
|
||||
You can import a module using the `import` keyword:
|
||||
|
||||
```js
|
||||
// File: ./ewwii.rhai
|
||||
|
||||
import "foo/baz" // just runs the script without importing it.
|
||||
import "foo/baz" as example; // runs the script and imports it into example.
|
||||
|
||||
// Access exported items
|
||||
print(example::greet()); // Greetings!
|
||||
print(example::PI); // 3.14159
|
||||
```
|
||||
|
||||
**Tip:** Always use the `as` keyword to import a script as a module with the name you desire.
|
||||
|
||||
## Notes
|
||||
|
||||
- Functions are automatically exported unless explicitly marked `private`.
|
||||
- Variables, constants, and objects must be exported using the `export` keyword.
|
||||
- `as` keyword is important in an `import` statement if you want to import the variables and functions in a Rhai file.
|
||||
@@ -1,15 +0,0 @@
|
||||
# Styling Widgets
|
||||
|
||||
Ewwii also allows writing inline styles to widgets using the `style` property. These styles are then applied at runtime using GTK's CSS system.
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
fn foo() {
|
||||
return box(#{
|
||||
style: "color: black; background-color: #fff;",
|
||||
}, [ label(#{ text: "baz" }) ]);
|
||||
}
|
||||
```
|
||||
|
||||
> This example makes the text color of all child widgets of box to black and sets the background color of the box to white (`#fff` is the hexadecimal for white).
|
||||
@@ -1,11 +0,0 @@
|
||||
# Theming & UI
|
||||
|
||||
This section focuses on visual styling and user interface customization. The core visual layer is built atop GTK, allowing deep integration with system themes while also enabling custom overrides through SCSS-style syntax.
|
||||
|
||||
We'll explore:
|
||||
|
||||
- How GTK theming works under the hood
|
||||
- Styling your widgets using theme classes and custom CSS
|
||||
- Layout implications of theme decisions (e.g., spacing, margins, z-order)
|
||||
|
||||
Whether you're trying to match your system's look or building a highly customized UI, this section gives you the tools to style confidently.
|
||||
@@ -1,27 +0,0 @@
|
||||
# Working with GTK
|
||||
|
||||
## Gtk Theming
|
||||
|
||||
Ewwii is styled in GTK CSS.
|
||||
You can use `Vanilla CSS` or `SCSS` to make theming even easier. The latter is compiled into CSS for you.
|
||||
If you don't know any way to style something check out the [GTK CSS Overview wiki](https://docs.gtk.org/gtk3/css-overview.html),
|
||||
the [GTK CSS Properties Overview wiki ](https://docs.gtk.org/gtk3/css-properties.html),
|
||||
or use the [GTK-Debugger](#gtk-debugger).
|
||||
|
||||
If you have **NO** clue about how to do CSS, check out some online guides or tutorials.
|
||||
|
||||
SCSS is _very_ close to CSS, so if you know CSS you'll have no problem learning SCSS.
|
||||
|
||||
## GTK Debugger
|
||||
|
||||
The debugger can be used for **a lot** of things, especially if something doesn't work or isn't styled right.
|
||||
|
||||
To open the GTK debugger, simply run
|
||||
|
||||
```bash
|
||||
ewwii inspector
|
||||
```
|
||||
|
||||
If a style or something similar doesn't work, you can click on the icon in the top left to select the thing that isn't being styled correctly.
|
||||
|
||||
Then you can click on the drop down menu in the top right corner and select CSS Nodes. Here you will see everything about styling it, CSS Properties, and how it's structured.
|
||||
@@ -1,35 +0,0 @@
|
||||
# Troubleshooting
|
||||
|
||||
Here you will find help if something doesn't work. If the issue isn't listed here, please [open an issue on the GitHub repo.](https://github.com/Ewwii-sh/ewwii/issues)
|
||||
|
||||
## Ewwii does not compile
|
||||
|
||||
1. Make sure that you are compiling ewwii using a recent version of rust (run `rustup update` to be sure you have the latest version available)
|
||||
2. Make sure you have all the necessary dependencies. If there are compile-errors, the compiler will tell you what you're missing.
|
||||
|
||||
## Ewwii does not work on Wayland
|
||||
|
||||
1. Make sure you compiled ewwii with the `--no-default-features --features=wayland` flags.
|
||||
2. Make sure that you're not trying to use X11-specific features (these are (hopefully) explicitly specified as such in the documentation).
|
||||
|
||||
## My configuration is not loaded correctly
|
||||
|
||||
1. Make sure the `ewwii.rhai` and `ewwii.(s)css` files are in the correct places.
|
||||
2. Sometimes, ewwii might fail to load your configuration as a result of a configuration error. Make sure your configuration is valid.
|
||||
|
||||
## Something isn't styled correctly!
|
||||
|
||||
Check the [GTK-Debugger](working_with_gtk.md#gtk-debugger) to get more insight into what styles GTK is applying to which elements.
|
||||
|
||||
## General issues
|
||||
|
||||
You should try the following things before opening an issue or doing more specialized troubleshooting:
|
||||
|
||||
- Kill the ewwii daemon by running `ewwii kill` and re-open your window with the `--debug`-flag to get additional log output.
|
||||
- Now you can take a look at the logs by running `ewwii logs`.
|
||||
- Use `ewwii state` to see the state of all variables.
|
||||
- Use `ewwii debug` to see the structure of your widget and other information.
|
||||
- Update to the latest ewwii version.
|
||||
- Sometimes hot reloading doesn't work. In that case, you can make use of `ewwii reload` manually.
|
||||
|
||||
Remember, if your issue isn't listed here, [open an issue on the GitHub repo](https://github.com/Ewwii-sh/ewwii/issues).
|
||||
@@ -1,254 +0,0 @@
|
||||
# Widget Properties
|
||||
|
||||
## widget
|
||||
|
||||
These properties apply to all widgets, and can be used anywhere!
|
||||
|
||||
**Properties**
|
||||
|
||||
- `class`: `string` css class name
|
||||
- `valign`: `string` how to align this vertically. possible values: "fill", "baseline", "center", "start", "end"
|
||||
- `halign`: `string` how to align this horizontally. possible values: "fill", "baseline", "center", "start", "end"
|
||||
- `vexpand`: `bool` should this container expand vertically. Default: false
|
||||
- `hexpand`: `bool` should this widget expand horizontally. Default: false
|
||||
- `width`: `int` width of this element
|
||||
- `height`: `int` height of this element
|
||||
- `active`: `bool` If this widget can be interacted with
|
||||
- `tooltip`: `string` tooltip text (on hover)
|
||||
- `visible`: `bool` visibility of the widget
|
||||
- `style`: `string` inline scss style applied to the widget
|
||||
- `css`: `string` scss code applied to the widget
|
||||
|
||||
## combo-box-text
|
||||
|
||||
**Properties**
|
||||
|
||||
- `items`: `vec` Items displayed in the combo box
|
||||
- `timeout`: `duration` timeout of the command. Default: "200ms"
|
||||
- `onchange`: `string` runs when an item is selected, replacing `{}` with the item
|
||||
|
||||
## expander
|
||||
|
||||
**Properties**
|
||||
|
||||
- `name`: `string` name of the expander
|
||||
- `expanded`: `bool` sets whether it's expanded
|
||||
|
||||
## revealer
|
||||
|
||||
**Properties**
|
||||
|
||||
- `transition`: `string` animation name ("slideright", "slideleft", etc.)
|
||||
- `reveal`: `bool` whether the child is revealed
|
||||
- `duration`: `duration` how long the transition lasts. Default: "500ms"
|
||||
|
||||
## checkbox
|
||||
|
||||
**Properties**
|
||||
|
||||
- `checked`: `bool` initial checked state
|
||||
- `timeout`: `duration` command timeout. Default: "200ms"
|
||||
- `onchecked`: `string` command when checked
|
||||
- `onunchecked`: `string` command when unchecked
|
||||
|
||||
## color-button
|
||||
|
||||
**Properties**
|
||||
|
||||
- `use_alpha`: `bool` use alpha channel
|
||||
- `onchange`: `string` command on color select
|
||||
- `timeout`: `duration` Default: "200ms"
|
||||
|
||||
## color-chooser
|
||||
|
||||
**Properties**
|
||||
|
||||
- `use_alpha`: `bool` use alpha channel
|
||||
- `onchange`: `string` command on color select
|
||||
- `timeout`: `duration` Default: "200ms"
|
||||
|
||||
## slider
|
||||
|
||||
**Properties**
|
||||
|
||||
- `flipped`: `bool` reverse direction
|
||||
- `marks`: `string` draw marks
|
||||
- `draw_value`: `bool` show value
|
||||
- `value_pos`: `string` where to show value ("left", "right", etc.)
|
||||
- `round_digits`: `int` number of decimal places
|
||||
- `value`: `float` current value
|
||||
- `min`: `float` minimum value
|
||||
- `max`: `float` maximum value
|
||||
- `timeout`: `duration` Default: "200ms"
|
||||
- `onchange`: `string` command on change (use `{}` for value)
|
||||
- `orientation`: `string` layout direction
|
||||
|
||||
## progress
|
||||
|
||||
**Properties**
|
||||
|
||||
- `flipped`: `bool` reverse direction
|
||||
- `value`: `float` progress (0–100)
|
||||
- `orientation`: `string` layout direction
|
||||
|
||||
## input
|
||||
|
||||
**NOTE:** This widget exposes a special environment variable `INPUT_VAL` to the commands specified in `onchange` and `onaccept`.
|
||||
|
||||
**Properties**
|
||||
|
||||
- `value`: `string` current text
|
||||
- `onchange`: `string` command on change; `INPUT_VAL` contains the new value
|
||||
- `timeout`: `duration` Default: "200ms"
|
||||
- `onaccept`: `string` command on Enter; `INPUT_VAL` contains the new value
|
||||
- `password`: `bool` obscure input
|
||||
|
||||
## button
|
||||
|
||||
**Properties**
|
||||
|
||||
- `timeout`: `duration` Default: "200ms"
|
||||
- `onclick`: `string` command on activation
|
||||
- `onmiddleclick`: `string` command on middle click
|
||||
- `onrightclick`: `string` command on right click
|
||||
|
||||
## image
|
||||
|
||||
**Properties**
|
||||
|
||||
- `path`: `string` image file path
|
||||
- `image_width`: `int` image width
|
||||
- `image_height`: `int` image height
|
||||
- `preserve_aspect_ratio`: `bool` keep aspect ratio
|
||||
- `fill_svg`: `string` fill color for SVGs
|
||||
- `icon`: `string` theme icon name
|
||||
- `icon_size`: `string` size of the icon
|
||||
|
||||
## box
|
||||
|
||||
**Properties**
|
||||
|
||||
- `spacing`: `int` spacing between children
|
||||
- `orientation`: `string` direction of children
|
||||
- `space_evenly`: `bool` distribute children evenly
|
||||
|
||||
## overlay
|
||||
|
||||
**Properties**
|
||||
|
||||
_None_
|
||||
|
||||
## tooltip
|
||||
|
||||
**Properties**
|
||||
|
||||
_None listed_
|
||||
|
||||
## centerbox
|
||||
|
||||
**Properties**
|
||||
|
||||
- `orientation`: `string` direction of layout
|
||||
|
||||
## scroll
|
||||
|
||||
**Properties**
|
||||
|
||||
- `hscroll`: `bool` allow horizontal scrolling
|
||||
- `vscroll`: `bool` allow vertical scrolling
|
||||
- `propagate_natural_height`: `bool` use the natural height if true
|
||||
|
||||
## eventbox
|
||||
|
||||
**Properties**
|
||||
|
||||
- `timeout`: `duration` Default: "200ms"
|
||||
- `onscroll`: `string` command on scroll (`{}` becomes direction)
|
||||
- `onhover`: `string` command on hover
|
||||
- `onhoverlost`: `string` command on hover exit
|
||||
- `cursor`: `string` cursor type
|
||||
- `ondropped`: `string` command on drop (`{}` is URI)
|
||||
- `dragvalue`: `string` URI to drag from this widget
|
||||
- `dragtype`: `string` type to drag ("file", "text")
|
||||
- `onclick`: `string` command on click
|
||||
- `onmiddleclick`: `string` command on middle click
|
||||
- `onrightclick`: `string` command on right click
|
||||
|
||||
## label
|
||||
|
||||
**Properties**
|
||||
|
||||
- `text`: `string` text to display
|
||||
- `truncate`: `bool` truncate text
|
||||
- `limit_width`: `int` max characters to show
|
||||
- `truncate_left`: `bool` truncate beginning
|
||||
- `show_truncated`: `bool` show truncation
|
||||
- `unindent`: `bool` strip leading spaces
|
||||
- `markup`: `string` Pango markup
|
||||
- `wrap`: `bool` wrap text
|
||||
- `angle`: `float` rotation angle
|
||||
- `gravity`: `string` text gravity
|
||||
- `xalign`: `float` horizontal alignment
|
||||
- `yalign`: `float` vertical alignment
|
||||
- `justify`: `string` text justification
|
||||
- `wrap_mode`: `string` wrap mode ("word", "char", etc.)
|
||||
- `lines`: `int` max lines (−1 = unlimited)
|
||||
|
||||
## literal
|
||||
|
||||
**Properties**
|
||||
|
||||
- `content`: `string` raw yuck
|
||||
|
||||
## calendar
|
||||
|
||||
**Properties**
|
||||
|
||||
- `day`: `float` selected day
|
||||
- `month`: `float` selected month
|
||||
- `year`: `float` selected year
|
||||
- `show_details`: `bool` show details
|
||||
- `show_heading`: `bool` show heading
|
||||
- `show_day_names`: `bool` show day names
|
||||
- `show_week_numbers`: `bool` show week numbers
|
||||
- `onclick`: `string` command with `{0}`, `{1}`, `{2}` for day/month/year
|
||||
- `timeout`: `duration` Default: "200ms"
|
||||
|
||||
## stack
|
||||
|
||||
**Properties**
|
||||
|
||||
- `selected`: `int` child index
|
||||
- `transition`: `string` animation name
|
||||
- `same_size`: `bool` equal child size
|
||||
|
||||
## transform
|
||||
|
||||
**Properties**
|
||||
|
||||
- `rotate`: `float` rotation angle
|
||||
- `transform_origin_x`: `string` transform origin x
|
||||
- `transform_origin_y`: `string` transform origin y
|
||||
- `translate_x`: `string` shift x
|
||||
- `translate_y`: `string` shift y
|
||||
- `scale-x`: `string` scale x
|
||||
- `scale-y`: `string` scale y
|
||||
|
||||
## circular-progress
|
||||
|
||||
**Properties**
|
||||
|
||||
- `value`: `float` 0–100 progress
|
||||
- `start_at`: `float` start percentage
|
||||
- `thickness`: `float` line thickness
|
||||
- `clockwise`: `bool` direction
|
||||
|
||||
## graph
|
||||
|
||||
**Properties**
|
||||
|
||||
- `value`: `float` current value
|
||||
- `thickness`: `float` line thickness
|
||||
- `time_range`: `duration` duration to track
|
||||
- `min`: `float` minimum value
|
||||
- `max`: `float` maximum value
|
||||
@@ -1,12 +0,0 @@
|
||||
# Widgets
|
||||
|
||||
Widgets are the building blocks of your interface. Each widget represents a visual element—text, images, containers, interactive components—that can be composed, styled, and updated dynamically.
|
||||
|
||||
This section introduces:
|
||||
|
||||
- The basic anatomy of a widget definition
|
||||
- How widgets are laid out within windows
|
||||
- The attributes and properties available to each widget type
|
||||
- Patterns for building reusable or dynamic widget trees
|
||||
|
||||
You’ll also learn how widget properties interact with the expression language and how reactivity is handled across updates.
|
||||
@@ -1,49 +0,0 @@
|
||||
# Widgets & Parameters
|
||||
|
||||
Below is a list of available widgets and the parameters each accepts.
|
||||
|
||||
## Widget Functions
|
||||
|
||||
These functions correspond to actual GTK widgets and render visible UI elements.
|
||||
|
||||
- **box**: `props`, `children`
|
||||
- **centerbox**: `props`, `children`
|
||||
- **eventbox**: `props`, `children`
|
||||
- **tooltip**: `props`, `children`
|
||||
- **circular_progress**: `props`
|
||||
- **graph**: `props`
|
||||
- **transform**: `props`
|
||||
- **slider**: `props`
|
||||
- **progress**: `props`
|
||||
- **image**: `props`
|
||||
- **button**: `props`
|
||||
- **label**: `props`
|
||||
- **input**: `props`
|
||||
- **calendar**: `props`
|
||||
- **color_button**: `props`
|
||||
- **expander**: `props`, `children`
|
||||
- **color_chooser**: `props`
|
||||
- **combo_box_text**: `props`
|
||||
- **checkbox**: `props`
|
||||
- **revealer**: `props`, `children`
|
||||
- **scroll**: `props`, `children`
|
||||
- **overlay**: `props`, `children`
|
||||
- **stack**: `props`, `children`
|
||||
|
||||
## Utility Functions
|
||||
|
||||
These are not visible UI widgets but are essential for layout, data binding, or dynamic behavior.
|
||||
|
||||
- **defwindow**: `string`, `props`, `children`
|
||||
- **poll**: `props`
|
||||
- **listen**: `props`
|
||||
|
||||
> **Let's recall**
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> `props` param: Defined in `#{}`
|
||||
>
|
||||
> `children` param: Defined in `[]`
|
||||
>
|
||||
> > Both of these are discussed in [chapter 2.2](../config/config_fundamentals.md)
|
||||
844
docs/theme/catppuccin.css
vendored
844
docs/theme/catppuccin.css
vendored
@@ -1,844 +0,0 @@
|
||||
/* https://highlightjs.readthedocs.io/en/latest/css-classes-reference.html */
|
||||
.latte.hljs {
|
||||
color: #4c4f69;
|
||||
background: #eff1f5;
|
||||
}
|
||||
.latte .hljs-keyword {
|
||||
color: #8839ef;
|
||||
}
|
||||
.latte .hljs-built_in {
|
||||
color: #d20f39;
|
||||
}
|
||||
.latte .hljs-type {
|
||||
color: #df8e1d;
|
||||
}
|
||||
.latte .hljs-literal {
|
||||
color: #fe640b;
|
||||
}
|
||||
.latte .hljs-number {
|
||||
color: #fe640b;
|
||||
}
|
||||
.latte .hljs-operator {
|
||||
color: #04a5e5;
|
||||
}
|
||||
.latte .hljs-punctuation {
|
||||
color: #5c5f77;
|
||||
}
|
||||
.latte .hljs-property {
|
||||
color: #179299;
|
||||
}
|
||||
.latte .hljs-regexp {
|
||||
color: #ea76cb;
|
||||
}
|
||||
.latte .hljs-string {
|
||||
color: #40a02b;
|
||||
}
|
||||
.latte .hljs-char.escape_ {
|
||||
color: #40a02b;
|
||||
}
|
||||
.latte .hljs-subst {
|
||||
color: #6c6f85;
|
||||
}
|
||||
.latte .hljs-symbol {
|
||||
color: #dd7878;
|
||||
}
|
||||
.latte .hljs-variable {
|
||||
color: #8839ef;
|
||||
}
|
||||
.latte .hljs-variable.language_ {
|
||||
color: #8839ef;
|
||||
}
|
||||
.latte .hljs-variable.constant_ {
|
||||
color: #fe640b;
|
||||
}
|
||||
.latte .hljs-title {
|
||||
color: #1e66f5;
|
||||
}
|
||||
.latte .hljs-title.class_ {
|
||||
color: #df8e1d;
|
||||
}
|
||||
.latte .hljs-title.function_ {
|
||||
color: #1e66f5;
|
||||
}
|
||||
.latte .hljs-params {
|
||||
color: #4c4f69;
|
||||
}
|
||||
.latte .hljs-comment {
|
||||
color: #7c7f93;
|
||||
}
|
||||
.latte .hljs-doctag {
|
||||
color: #d20f39;
|
||||
}
|
||||
.latte .hljs-meta {
|
||||
color: #fe640b;
|
||||
}
|
||||
.latte .hljs-section {
|
||||
color: #1e66f5;
|
||||
}
|
||||
.latte .hljs-tag {
|
||||
color: #179299;
|
||||
}
|
||||
.latte .hljs-name {
|
||||
color: #8839ef;
|
||||
}
|
||||
.latte .hljs-attr {
|
||||
color: #1e66f5;
|
||||
}
|
||||
.latte .hljs-attribute {
|
||||
color: #40a02b;
|
||||
}
|
||||
.latte .hljs-bullet {
|
||||
color: #179299;
|
||||
}
|
||||
.latte .hljs-code {
|
||||
color: #40a02b;
|
||||
}
|
||||
.latte .hljs-emphasis {
|
||||
color: #d20f39;
|
||||
font-style: italic;
|
||||
}
|
||||
.latte .hljs-strong {
|
||||
color: #d20f39;
|
||||
font-weight: bold;
|
||||
}
|
||||
.latte .hljs-formula {
|
||||
color: #179299;
|
||||
}
|
||||
.latte .hljs-link {
|
||||
color: #209fb5;
|
||||
font-style: italic;
|
||||
}
|
||||
.latte .hljs-quote {
|
||||
color: #40a02b;
|
||||
font-style: italic;
|
||||
}
|
||||
.latte .hljs-selector-tag {
|
||||
color: #df8e1d;
|
||||
}
|
||||
.latte .hljs-selector-id {
|
||||
color: #1e66f5;
|
||||
}
|
||||
.latte .hljs-selector-class {
|
||||
color: #179299;
|
||||
}
|
||||
.latte .hljs-selector-attr {
|
||||
color: #8839ef;
|
||||
}
|
||||
.latte .hljs-selector-pseudo {
|
||||
color: #179299;
|
||||
}
|
||||
.latte .hljs-template-tag {
|
||||
color: #dd7878;
|
||||
}
|
||||
.latte .hljs-template-variable {
|
||||
color: #dd7878;
|
||||
}
|
||||
.latte .hljs-addition {
|
||||
color: #40a02b;
|
||||
background: rgba(64, 160, 43, 0.15);
|
||||
}
|
||||
.latte .hljs-deletion {
|
||||
color: #d20f39;
|
||||
background: rgba(210, 15, 57, 0.15);
|
||||
}
|
||||
.latte :is(h1, h2, h3, h4, h5, h6) a code {
|
||||
color: #4c4f69;
|
||||
}
|
||||
.latte a code {
|
||||
color: #1e66f5;
|
||||
}
|
||||
.latte code {
|
||||
color: #4c4f69;
|
||||
background: #e6e9ef;
|
||||
}
|
||||
.latte blockquote blockquote {
|
||||
border-top: 0.1em solid #acb0be;
|
||||
border-bottom: 0.1em solid #acb0be;
|
||||
}
|
||||
.latte hr {
|
||||
border-color: #acb0be;
|
||||
border-style: solid;
|
||||
}
|
||||
.latte del {
|
||||
color: #7c7f93;
|
||||
}
|
||||
.latte .ace_gutter {
|
||||
color: #8c8fa1;
|
||||
background: #e6e9ef;
|
||||
}
|
||||
.latte .ace_gutter-active-line.ace_gutter-cell {
|
||||
color: #ea76cb;
|
||||
background: #e6e9ef;
|
||||
}
|
||||
.latte .tooltiptext {
|
||||
background: #e6e9ef;
|
||||
color: #4c4f69;
|
||||
}
|
||||
|
||||
.frappe.hljs {
|
||||
color: #c6d0f5;
|
||||
background: #303446;
|
||||
}
|
||||
.frappe .hljs-keyword {
|
||||
color: #ca9ee6;
|
||||
}
|
||||
.frappe .hljs-built_in {
|
||||
color: #e78284;
|
||||
}
|
||||
.frappe .hljs-type {
|
||||
color: #e5c890;
|
||||
}
|
||||
.frappe .hljs-literal {
|
||||
color: #ef9f76;
|
||||
}
|
||||
.frappe .hljs-number {
|
||||
color: #ef9f76;
|
||||
}
|
||||
.frappe .hljs-operator {
|
||||
color: #99d1db;
|
||||
}
|
||||
.frappe .hljs-punctuation {
|
||||
color: #b5bfe2;
|
||||
}
|
||||
.frappe .hljs-property {
|
||||
color: #81c8be;
|
||||
}
|
||||
.frappe .hljs-regexp {
|
||||
color: #f4b8e4;
|
||||
}
|
||||
.frappe .hljs-string {
|
||||
color: #a6d189;
|
||||
}
|
||||
.frappe .hljs-char.escape_ {
|
||||
color: #a6d189;
|
||||
}
|
||||
.frappe .hljs-subst {
|
||||
color: #a5adce;
|
||||
}
|
||||
.frappe .hljs-symbol {
|
||||
color: #eebebe;
|
||||
}
|
||||
.frappe .hljs-variable {
|
||||
color: #ca9ee6;
|
||||
}
|
||||
.frappe .hljs-variable.language_ {
|
||||
color: #ca9ee6;
|
||||
}
|
||||
.frappe .hljs-variable.constant_ {
|
||||
color: #ef9f76;
|
||||
}
|
||||
.frappe .hljs-title {
|
||||
color: #8caaee;
|
||||
}
|
||||
.frappe .hljs-title.class_ {
|
||||
color: #e5c890;
|
||||
}
|
||||
.frappe .hljs-title.function_ {
|
||||
color: #8caaee;
|
||||
}
|
||||
.frappe .hljs-params {
|
||||
color: #c6d0f5;
|
||||
}
|
||||
.frappe .hljs-comment {
|
||||
color: #949cbb;
|
||||
}
|
||||
.frappe .hljs-doctag {
|
||||
color: #e78284;
|
||||
}
|
||||
.frappe .hljs-meta {
|
||||
color: #ef9f76;
|
||||
}
|
||||
.frappe .hljs-section {
|
||||
color: #8caaee;
|
||||
}
|
||||
.frappe .hljs-tag {
|
||||
color: #81c8be;
|
||||
}
|
||||
.frappe .hljs-name {
|
||||
color: #ca9ee6;
|
||||
}
|
||||
.frappe .hljs-attr {
|
||||
color: #8caaee;
|
||||
}
|
||||
.frappe .hljs-attribute {
|
||||
color: #a6d189;
|
||||
}
|
||||
.frappe .hljs-bullet {
|
||||
color: #81c8be;
|
||||
}
|
||||
.frappe .hljs-code {
|
||||
color: #a6d189;
|
||||
}
|
||||
.frappe .hljs-emphasis {
|
||||
color: #e78284;
|
||||
font-style: italic;
|
||||
}
|
||||
.frappe .hljs-strong {
|
||||
color: #e78284;
|
||||
font-weight: bold;
|
||||
}
|
||||
.frappe .hljs-formula {
|
||||
color: #81c8be;
|
||||
}
|
||||
.frappe .hljs-link {
|
||||
color: #85c1dc;
|
||||
font-style: italic;
|
||||
}
|
||||
.frappe .hljs-quote {
|
||||
color: #a6d189;
|
||||
font-style: italic;
|
||||
}
|
||||
.frappe .hljs-selector-tag {
|
||||
color: #e5c890;
|
||||
}
|
||||
.frappe .hljs-selector-id {
|
||||
color: #8caaee;
|
||||
}
|
||||
.frappe .hljs-selector-class {
|
||||
color: #81c8be;
|
||||
}
|
||||
.frappe .hljs-selector-attr {
|
||||
color: #ca9ee6;
|
||||
}
|
||||
.frappe .hljs-selector-pseudo {
|
||||
color: #81c8be;
|
||||
}
|
||||
.frappe .hljs-template-tag {
|
||||
color: #eebebe;
|
||||
}
|
||||
.frappe .hljs-template-variable {
|
||||
color: #eebebe;
|
||||
}
|
||||
.frappe .hljs-addition {
|
||||
color: #a6d189;
|
||||
background: rgba(166, 209, 137, 0.15);
|
||||
}
|
||||
.frappe .hljs-deletion {
|
||||
color: #e78284;
|
||||
background: rgba(231, 130, 132, 0.15);
|
||||
}
|
||||
.frappe :is(h1, h2, h3, h4, h5, h6) a code {
|
||||
color: #c6d0f5;
|
||||
}
|
||||
.frappe a code {
|
||||
color: #8caaee;
|
||||
}
|
||||
.frappe code {
|
||||
color: #c6d0f5;
|
||||
background: #292c3c;
|
||||
}
|
||||
.frappe blockquote blockquote {
|
||||
border-top: 0.1em solid #626880;
|
||||
border-bottom: 0.1em solid #626880;
|
||||
}
|
||||
.frappe hr {
|
||||
border-color: #626880;
|
||||
border-style: solid;
|
||||
}
|
||||
.frappe del {
|
||||
color: #949cbb;
|
||||
}
|
||||
.frappe .ace_gutter {
|
||||
color: #838ba7;
|
||||
background: #292c3c;
|
||||
}
|
||||
.frappe .ace_gutter-active-line.ace_gutter-cell {
|
||||
color: #f4b8e4;
|
||||
background: #292c3c;
|
||||
}
|
||||
.frappe .tooltiptext {
|
||||
background: #292c3c;
|
||||
color: #c6d0f5;
|
||||
}
|
||||
|
||||
.macchiato.hljs {
|
||||
color: #cad3f5;
|
||||
background: #24273a;
|
||||
}
|
||||
.macchiato .hljs-keyword {
|
||||
color: #c6a0f6;
|
||||
}
|
||||
.macchiato .hljs-built_in {
|
||||
color: #ed8796;
|
||||
}
|
||||
.macchiato .hljs-type {
|
||||
color: #eed49f;
|
||||
}
|
||||
.macchiato .hljs-literal {
|
||||
color: #f5a97f;
|
||||
}
|
||||
.macchiato .hljs-number {
|
||||
color: #f5a97f;
|
||||
}
|
||||
.macchiato .hljs-operator {
|
||||
color: #91d7e3;
|
||||
}
|
||||
.macchiato .hljs-punctuation {
|
||||
color: #b8c0e0;
|
||||
}
|
||||
.macchiato .hljs-property {
|
||||
color: #8bd5ca;
|
||||
}
|
||||
.macchiato .hljs-regexp {
|
||||
color: #f5bde6;
|
||||
}
|
||||
.macchiato .hljs-string {
|
||||
color: #a6da95;
|
||||
}
|
||||
.macchiato .hljs-char.escape_ {
|
||||
color: #a6da95;
|
||||
}
|
||||
.macchiato .hljs-subst {
|
||||
color: #a5adcb;
|
||||
}
|
||||
.macchiato .hljs-symbol {
|
||||
color: #f0c6c6;
|
||||
}
|
||||
.macchiato .hljs-variable {
|
||||
color: #c6a0f6;
|
||||
}
|
||||
.macchiato .hljs-variable.language_ {
|
||||
color: #c6a0f6;
|
||||
}
|
||||
.macchiato .hljs-variable.constant_ {
|
||||
color: #f5a97f;
|
||||
}
|
||||
.macchiato .hljs-title {
|
||||
color: #8aadf4;
|
||||
}
|
||||
.macchiato .hljs-title.class_ {
|
||||
color: #eed49f;
|
||||
}
|
||||
.macchiato .hljs-title.function_ {
|
||||
color: #8aadf4;
|
||||
}
|
||||
.macchiato .hljs-params {
|
||||
color: #cad3f5;
|
||||
}
|
||||
.macchiato .hljs-comment {
|
||||
color: #939ab7;
|
||||
}
|
||||
.macchiato .hljs-doctag {
|
||||
color: #ed8796;
|
||||
}
|
||||
.macchiato .hljs-meta {
|
||||
color: #f5a97f;
|
||||
}
|
||||
.macchiato .hljs-section {
|
||||
color: #8aadf4;
|
||||
}
|
||||
.macchiato .hljs-tag {
|
||||
color: #8bd5ca;
|
||||
}
|
||||
.macchiato .hljs-name {
|
||||
color: #c6a0f6;
|
||||
}
|
||||
.macchiato .hljs-attr {
|
||||
color: #8aadf4;
|
||||
}
|
||||
.macchiato .hljs-attribute {
|
||||
color: #a6da95;
|
||||
}
|
||||
.macchiato .hljs-bullet {
|
||||
color: #8bd5ca;
|
||||
}
|
||||
.macchiato .hljs-code {
|
||||
color: #a6da95;
|
||||
}
|
||||
.macchiato .hljs-emphasis {
|
||||
color: #ed8796;
|
||||
font-style: italic;
|
||||
}
|
||||
.macchiato .hljs-strong {
|
||||
color: #ed8796;
|
||||
font-weight: bold;
|
||||
}
|
||||
.macchiato .hljs-formula {
|
||||
color: #8bd5ca;
|
||||
}
|
||||
.macchiato .hljs-link {
|
||||
color: #7dc4e4;
|
||||
font-style: italic;
|
||||
}
|
||||
.macchiato .hljs-quote {
|
||||
color: #a6da95;
|
||||
font-style: italic;
|
||||
}
|
||||
.macchiato .hljs-selector-tag {
|
||||
color: #eed49f;
|
||||
}
|
||||
.macchiato .hljs-selector-id {
|
||||
color: #8aadf4;
|
||||
}
|
||||
.macchiato .hljs-selector-class {
|
||||
color: #8bd5ca;
|
||||
}
|
||||
.macchiato .hljs-selector-attr {
|
||||
color: #c6a0f6;
|
||||
}
|
||||
.macchiato .hljs-selector-pseudo {
|
||||
color: #8bd5ca;
|
||||
}
|
||||
.macchiato .hljs-template-tag {
|
||||
color: #f0c6c6;
|
||||
}
|
||||
.macchiato .hljs-template-variable {
|
||||
color: #f0c6c6;
|
||||
}
|
||||
.macchiato .hljs-addition {
|
||||
color: #a6da95;
|
||||
background: rgba(166, 218, 149, 0.15);
|
||||
}
|
||||
.macchiato .hljs-deletion {
|
||||
color: #ed8796;
|
||||
background: rgba(237, 135, 150, 0.15);
|
||||
}
|
||||
.macchiato :is(h1, h2, h3, h4, h5, h6) a code {
|
||||
color: #cad3f5;
|
||||
}
|
||||
.macchiato a code {
|
||||
color: #8aadf4;
|
||||
}
|
||||
.macchiato code {
|
||||
color: #cad3f5;
|
||||
background: #1e2030;
|
||||
}
|
||||
.macchiato blockquote blockquote {
|
||||
border-top: 0.1em solid #5b6078;
|
||||
border-bottom: 0.1em solid #5b6078;
|
||||
}
|
||||
.macchiato hr {
|
||||
border-color: #5b6078;
|
||||
border-style: solid;
|
||||
}
|
||||
.macchiato del {
|
||||
color: #939ab7;
|
||||
}
|
||||
.macchiato .ace_gutter {
|
||||
color: #8087a2;
|
||||
background: #1e2030;
|
||||
}
|
||||
.macchiato .ace_gutter-active-line.ace_gutter-cell {
|
||||
color: #f5bde6;
|
||||
background: #1e2030;
|
||||
}
|
||||
.macchiato .tooltiptext {
|
||||
background: #1e2030;
|
||||
color: #cad3f5;
|
||||
}
|
||||
|
||||
.mocha.hljs {
|
||||
color: #cdd6f4;
|
||||
background: #1e1e2e;
|
||||
}
|
||||
.mocha .hljs-keyword {
|
||||
color: #cba6f7;
|
||||
}
|
||||
.mocha .hljs-built_in {
|
||||
color: #f38ba8;
|
||||
}
|
||||
.mocha .hljs-type {
|
||||
color: #f9e2af;
|
||||
}
|
||||
.mocha .hljs-literal {
|
||||
color: #fab387;
|
||||
}
|
||||
.mocha .hljs-number {
|
||||
color: #fab387;
|
||||
}
|
||||
.mocha .hljs-operator {
|
||||
color: #89dceb;
|
||||
}
|
||||
.mocha .hljs-punctuation {
|
||||
color: #bac2de;
|
||||
}
|
||||
.mocha .hljs-property {
|
||||
color: #94e2d5;
|
||||
}
|
||||
.mocha .hljs-regexp {
|
||||
color: #f5c2e7;
|
||||
}
|
||||
.mocha .hljs-string {
|
||||
color: #a6e3a1;
|
||||
}
|
||||
.mocha .hljs-char.escape_ {
|
||||
color: #a6e3a1;
|
||||
}
|
||||
.mocha .hljs-subst {
|
||||
color: #a6adc8;
|
||||
}
|
||||
.mocha .hljs-symbol {
|
||||
color: #f2cdcd;
|
||||
}
|
||||
.mocha .hljs-variable {
|
||||
color: #cba6f7;
|
||||
}
|
||||
.mocha .hljs-variable.language_ {
|
||||
color: #cba6f7;
|
||||
}
|
||||
.mocha .hljs-variable.constant_ {
|
||||
color: #fab387;
|
||||
}
|
||||
.mocha .hljs-title {
|
||||
color: #89b4fa;
|
||||
}
|
||||
.mocha .hljs-title.class_ {
|
||||
color: #f9e2af;
|
||||
}
|
||||
.mocha .hljs-title.function_ {
|
||||
color: #89b4fa;
|
||||
}
|
||||
.mocha .hljs-params {
|
||||
color: #cdd6f4;
|
||||
}
|
||||
.mocha .hljs-comment {
|
||||
color: #9399b2;
|
||||
}
|
||||
.mocha .hljs-doctag {
|
||||
color: #f38ba8;
|
||||
}
|
||||
.mocha .hljs-meta {
|
||||
color: #fab387;
|
||||
}
|
||||
.mocha .hljs-section {
|
||||
color: #89b4fa;
|
||||
}
|
||||
.mocha .hljs-tag {
|
||||
color: #94e2d5;
|
||||
}
|
||||
.mocha .hljs-name {
|
||||
color: #cba6f7;
|
||||
}
|
||||
.mocha .hljs-attr {
|
||||
color: #89b4fa;
|
||||
}
|
||||
.mocha .hljs-attribute {
|
||||
color: #a6e3a1;
|
||||
}
|
||||
.mocha .hljs-bullet {
|
||||
color: #94e2d5;
|
||||
}
|
||||
.mocha .hljs-code {
|
||||
color: #a6e3a1;
|
||||
}
|
||||
.mocha .hljs-emphasis {
|
||||
color: #f38ba8;
|
||||
font-style: italic;
|
||||
}
|
||||
.mocha .hljs-strong {
|
||||
color: #f38ba8;
|
||||
font-weight: bold;
|
||||
}
|
||||
.mocha .hljs-formula {
|
||||
color: #94e2d5;
|
||||
}
|
||||
.mocha .hljs-link {
|
||||
color: #74c7ec;
|
||||
font-style: italic;
|
||||
}
|
||||
.mocha .hljs-quote {
|
||||
color: #a6e3a1;
|
||||
font-style: italic;
|
||||
}
|
||||
.mocha .hljs-selector-tag {
|
||||
color: #f9e2af;
|
||||
}
|
||||
.mocha .hljs-selector-id {
|
||||
color: #89b4fa;
|
||||
}
|
||||
.mocha .hljs-selector-class {
|
||||
color: #94e2d5;
|
||||
}
|
||||
.mocha .hljs-selector-attr {
|
||||
color: #cba6f7;
|
||||
}
|
||||
.mocha .hljs-selector-pseudo {
|
||||
color: #94e2d5;
|
||||
}
|
||||
.mocha .hljs-template-tag {
|
||||
color: #f2cdcd;
|
||||
}
|
||||
.mocha .hljs-template-variable {
|
||||
color: #f2cdcd;
|
||||
}
|
||||
.mocha .hljs-addition {
|
||||
color: #a6e3a1;
|
||||
background: rgba(166, 227, 161, 0.15);
|
||||
}
|
||||
.mocha .hljs-deletion {
|
||||
color: #f38ba8;
|
||||
background: rgba(243, 139, 168, 0.15);
|
||||
}
|
||||
.mocha :is(h1, h2, h3, h4, h5, h6) a code {
|
||||
color: #cdd6f4;
|
||||
}
|
||||
.mocha a code {
|
||||
color: #89b4fa;
|
||||
}
|
||||
.mocha code {
|
||||
color: #cdd6f4;
|
||||
background: #181825;
|
||||
}
|
||||
.mocha blockquote blockquote {
|
||||
border-top: 0.1em solid #585b70;
|
||||
border-bottom: 0.1em solid #585b70;
|
||||
}
|
||||
.mocha hr {
|
||||
border-color: #585b70;
|
||||
border-style: solid;
|
||||
}
|
||||
.mocha del {
|
||||
color: #9399b2;
|
||||
}
|
||||
.mocha .ace_gutter {
|
||||
color: #7f849c;
|
||||
background: #181825;
|
||||
}
|
||||
.mocha .ace_gutter-active-line.ace_gutter-cell {
|
||||
color: #f5c2e7;
|
||||
background: #181825;
|
||||
}
|
||||
.mocha .tooltiptext {
|
||||
background: #181825;
|
||||
color: #cdd6f4;
|
||||
}
|
||||
|
||||
.latte {
|
||||
--bg: #eff1f5;
|
||||
--fg: #4c4f69;
|
||||
--sidebar-bg: #e6e9ef;
|
||||
--sidebar-fg: #4c4f69;
|
||||
--sidebar-non-existant: #9ca0b0;
|
||||
--sidebar-active: #1e66f5;
|
||||
--sidebar-spacer: #9ca0b0;
|
||||
--scrollbar: #9ca0b0;
|
||||
--icons: #9ca0b0;
|
||||
--icons-hover: #7c7f93;
|
||||
--links: #1e66f5;
|
||||
--inline-code-color: #4c4f69;
|
||||
--theme-popup-bg: #e6e9ef;
|
||||
--theme-popup-border: #9ca0b0;
|
||||
--theme-hover: #ccd0da;
|
||||
--quote-bg: #e6e9ef;
|
||||
--quote-border: #dce0e8;
|
||||
--table-border-color: #ccd0da;
|
||||
--table-header-bg: #e6e9ef;
|
||||
--table-alternate-bg: #e6e9ef;
|
||||
--searchbar-border-color: #ccd0da;
|
||||
--searchbar-bg: #e6e9ef;
|
||||
--searchbar-fg: #4c4f69;
|
||||
--searchbar-shadow-color: #dce0e8;
|
||||
--searchresults-header-fg: #4c4f69;
|
||||
--searchresults-border-color: #ccd0da;
|
||||
--searchresults-li-bg: #eff1f5;
|
||||
--search-mark-bg: #fe640b;
|
||||
--warning-border: #fe640b;
|
||||
--color-scheme: light;
|
||||
--copy-button-filter: brightness(0) saturate(100%) invert(47%) sepia(6%) saturate(1263%) hue-rotate(195deg) brightness(90%) contrast(81%);
|
||||
--copy-button-filter-hover: brightness(0) saturate(100%) invert(30%) sepia(80%) saturate(1850%) hue-rotate(209deg) brightness(94%) contrast(105%);
|
||||
}
|
||||
|
||||
.frappe {
|
||||
--bg: #303446;
|
||||
--fg: #c6d0f5;
|
||||
--sidebar-bg: #292c3c;
|
||||
--sidebar-fg: #c6d0f5;
|
||||
--sidebar-non-existant: #737994;
|
||||
--sidebar-active: #8caaee;
|
||||
--sidebar-spacer: #737994;
|
||||
--scrollbar: #737994;
|
||||
--icons: #737994;
|
||||
--icons-hover: #949cbb;
|
||||
--links: #8caaee;
|
||||
--inline-code-color: #c6d0f5;
|
||||
--theme-popup-bg: #292c3c;
|
||||
--theme-popup-border: #737994;
|
||||
--theme-hover: #414559;
|
||||
--quote-bg: #292c3c;
|
||||
--quote-border: #232634;
|
||||
--table-border-color: #414559;
|
||||
--table-header-bg: #292c3c;
|
||||
--table-alternate-bg: #292c3c;
|
||||
--searchbar-border-color: #414559;
|
||||
--searchbar-bg: #292c3c;
|
||||
--searchbar-fg: #c6d0f5;
|
||||
--searchbar-shadow-color: #232634;
|
||||
--searchresults-header-fg: #c6d0f5;
|
||||
--searchresults-border-color: #414559;
|
||||
--searchresults-li-bg: #303446;
|
||||
--search-mark-bg: #ef9f76;
|
||||
--warning-border: #ef9f76;
|
||||
--color-scheme: dark;
|
||||
--copy-button-filter: brightness(0) saturate(100%) invert(82%) sepia(6%) saturate(1287%) hue-rotate(192deg) brightness(86%) contrast(85%);
|
||||
--copy-button-filter-hover: brightness(0) saturate(100%) invert(68%) sepia(16%) saturate(1070%) hue-rotate(185deg) brightness(96%) contrast(95%);
|
||||
}
|
||||
|
||||
.macchiato {
|
||||
--bg: #24273a;
|
||||
--fg: #cad3f5;
|
||||
--sidebar-bg: #1e2030;
|
||||
--sidebar-fg: #cad3f5;
|
||||
--sidebar-non-existant: #6e738d;
|
||||
--sidebar-active: #8aadf4;
|
||||
--sidebar-spacer: #6e738d;
|
||||
--scrollbar: #6e738d;
|
||||
--icons: #6e738d;
|
||||
--icons-hover: #939ab7;
|
||||
--links: #8aadf4;
|
||||
--inline-code-color: #cad3f5;
|
||||
--theme-popup-bg: #1e2030;
|
||||
--theme-popup-border: #6e738d;
|
||||
--theme-hover: #363a4f;
|
||||
--quote-bg: #1e2030;
|
||||
--quote-border: #181926;
|
||||
--table-border-color: #363a4f;
|
||||
--table-header-bg: #1e2030;
|
||||
--table-alternate-bg: #1e2030;
|
||||
--searchbar-border-color: #363a4f;
|
||||
--searchbar-bg: #1e2030;
|
||||
--searchbar-fg: #cad3f5;
|
||||
--searchbar-shadow-color: #181926;
|
||||
--searchresults-header-fg: #cad3f5;
|
||||
--searchresults-border-color: #363a4f;
|
||||
--searchresults-li-bg: #24273a;
|
||||
--search-mark-bg: #f5a97f;
|
||||
--warning-border: #f5a97f;
|
||||
--color-scheme: dark;
|
||||
--copy-button-filter: brightness(0) saturate(100%) invert(75%) sepia(18%) saturate(361%) hue-rotate(190deg) brightness(91%) contrast(86%);
|
||||
--copy-button-filter-hover: brightness(0) saturate(100%) invert(67%) sepia(17%) saturate(1007%) hue-rotate(183deg) brightness(99%) contrast(94%);
|
||||
}
|
||||
|
||||
.mocha {
|
||||
--bg: #1e1e2e;
|
||||
--fg: #cdd6f4;
|
||||
--sidebar-bg: #181825;
|
||||
--sidebar-fg: #cdd6f4;
|
||||
--sidebar-non-existant: #6c7086;
|
||||
--sidebar-active: #89b4fa;
|
||||
--sidebar-spacer: #6c7086;
|
||||
--scrollbar: #6c7086;
|
||||
--icons: #6c7086;
|
||||
--icons-hover: #9399b2;
|
||||
--links: #89b4fa;
|
||||
--inline-code-color: #cdd6f4;
|
||||
--theme-popup-bg: #181825;
|
||||
--theme-popup-border: #6c7086;
|
||||
--theme-hover: #313244;
|
||||
--quote-bg: #181825;
|
||||
--quote-border: #11111b;
|
||||
--table-border-color: #313244;
|
||||
--table-header-bg: #181825;
|
||||
--table-alternate-bg: #181825;
|
||||
--searchbar-border-color: #313244;
|
||||
--searchbar-bg: #181825;
|
||||
--searchbar-fg: #cdd6f4;
|
||||
--searchbar-shadow-color: #11111b;
|
||||
--searchresults-header-fg: #cdd6f4;
|
||||
--searchresults-border-color: #313244;
|
||||
--searchresults-li-bg: #1e1e2e;
|
||||
--search-mark-bg: #fab387;
|
||||
--warning-border: #fab387;
|
||||
--color-scheme: dark;
|
||||
--copy-button-filter: brightness(0) saturate(100%) invert(84%) sepia(9%) saturate(767%) hue-rotate(192deg) brightness(84%) contrast(84%);
|
||||
--copy-button-filter-hover: brightness(0) saturate(100%) invert(68%) sepia(18%) saturate(951%) hue-rotate(180deg) brightness(98%) contrast(100%);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user