Compare commits
616 Commits
v0.3.0-bet
...
main
| 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 |
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
|
||||
|
||||
BIN
.github/screenshots/ewifi_gui_screenshot.png
vendored
BIN
.github/screenshots/ewifi_gui_screenshot.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 500 KiB |
79
.github/workflows/build.yml
vendored
79
.github/workflows/build.yml
vendored
@@ -1,51 +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 GTK4 dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
libgtk-4-dev \
|
||||
libgtk-layer-shell-dev \
|
||||
pkg-config \
|
||||
build-essential
|
||||
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
|
||||
|
||||
62
CHANGELOG.md
62
CHANGELOG.md
@@ -5,6 +5,68 @@ 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
|
||||
|
||||
39
Cargo.lock
generated
39
Cargo.lock
generated
@@ -486,7 +486,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ewwii"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -515,6 +515,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shared_utils",
|
||||
"shell-words",
|
||||
"simple-signal",
|
||||
"smart-default",
|
||||
"static_assertions",
|
||||
@@ -527,7 +528,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ewwii_plugin_api"
|
||||
version = "0.4.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"gtk4",
|
||||
"rhai",
|
||||
@@ -1636,9 +1637,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
version = "1.0.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -1719,9 +1720,9 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||
|
||||
[[package]]
|
||||
name = "rhai"
|
||||
version = "1.23.4"
|
||||
version = "1.23.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527390cc333a8d2cd8237890e15c36518c26f8b54c903d86fc59f42f08d25594"
|
||||
checksum = "f4e35aaaa439a5bda2f8d15251bc375e4edfac75f9865734644782c9701b5709"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bitflags 2.9.4",
|
||||
@@ -1766,6 +1767,7 @@ dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
"codespan-reporting",
|
||||
"gtk4",
|
||||
"libc",
|
||||
"log",
|
||||
"nix",
|
||||
@@ -1773,6 +1775,7 @@ dependencies = [
|
||||
"regex",
|
||||
"rhai",
|
||||
"rhai_trace",
|
||||
"scan_prop_proc",
|
||||
"serde",
|
||||
"shared_utils",
|
||||
"tokio",
|
||||
@@ -1780,9 +1783,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rhai_trace"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90093b2d5beff618cbf5698c4952ecca7eca5c5a6d9a892c0d19d9046d8329e4"
|
||||
checksum = "4415c698df298d65e46e6d73905afc1b08e2fb60e68b1850c1af9e7dc499d467"
|
||||
dependencies = [
|
||||
"rhai",
|
||||
]
|
||||
@@ -1830,6 +1833,15 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scan_prop_proc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@@ -1909,10 +1921,17 @@ name = "shared_utils"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"once_cell",
|
||||
"rhai",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shell-words"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
@@ -2003,9 +2022,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
version = "2.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
13
Cargo.toml
13
Cargo.toml
@@ -1,12 +1,13 @@
|
||||
[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" }
|
||||
ewwii_plugin_api = { version = "0.4.0", path = "crates/ewwii_plugin_api" }
|
||||
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"
|
||||
@@ -25,7 +26,7 @@ derive_more = { version = "1", features = [
|
||||
extend = "1.2"
|
||||
futures = "0.3.30"
|
||||
grass = "0.13.4"
|
||||
gtk4 = "0.10.1"
|
||||
gtk4 = { version = "0.10.1", features = ["v4_8"] }
|
||||
itertools = "0.13.0"
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
@@ -35,7 +36,7 @@ once_cell = "1.19"
|
||||
pretty_assertions = "1.4.0"
|
||||
pretty_env_logger = "0.5.0"
|
||||
regex = "1.10.5"
|
||||
rhai = { version = "1.22.2" }
|
||||
rhai = "1.23.6"
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
simple-signal = "1.1"
|
||||
@@ -45,6 +46,10 @@ 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/.github/screenshots/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.3.0"
|
||||
version = "0.4.0"
|
||||
authors = ["byson94 <byson94wastaken@gmail.com>"]
|
||||
description = "Widgets for everyone made better!"
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -17,7 +17,7 @@ wayland = ["gtk4-layer-shell"]
|
||||
[dependencies]
|
||||
shared_utils.workspace = true
|
||||
rhai_impl.workspace = true
|
||||
ewwii_plugin_api = { workspace = true }
|
||||
ewwii_plugin_api.workspace = true
|
||||
|
||||
gtk4-layer-shell = { version = "0.6.3", optional = true }
|
||||
gdk4-x11 = { version = "0.10.1", optional = true }
|
||||
@@ -49,7 +49,8 @@ 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"
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ use crate::{
|
||||
*,
|
||||
};
|
||||
use anyhow::{anyhow, bail};
|
||||
use ewwii_plugin_api as epapi;
|
||||
use gdk::Monitor;
|
||||
use gtk4::Window;
|
||||
use gtk4::{gdk, glib};
|
||||
@@ -84,9 +85,14 @@ pub enum DaemonCommand {
|
||||
ShowState(DaemonResponseSender),
|
||||
ListWindows(DaemonResponseSender),
|
||||
ListActiveWindows(DaemonResponseSender),
|
||||
WidgetControl {
|
||||
action: crate::opts::WidgetControlAction,
|
||||
sender: DaemonResponseSender,
|
||||
},
|
||||
TriggerUpdateUI {
|
||||
inject_vars: Option<HashMap<String, String>>,
|
||||
should_preserve_state: bool,
|
||||
lifetime: Option<String>,
|
||||
sender: DaemonResponseSender,
|
||||
},
|
||||
CallRhaiFns {
|
||||
@@ -153,6 +159,7 @@ pub struct App<B: DisplayBackend> {
|
||||
/// When reloading the config, these should be opened again.
|
||||
pub failed_windows: HashSet<String>,
|
||||
pub css_provider: gtk4::CssProvider,
|
||||
pub reloading: bool,
|
||||
|
||||
/// Sender to send [`DaemonCommand`]s
|
||||
pub app_evt_send: UnboundedSender<DaemonCommand>,
|
||||
@@ -165,6 +172,7 @@ pub struct App<B: DisplayBackend> {
|
||||
|
||||
// The cached store of poll/listen handlers
|
||||
pub pl_handler_store: rhai_impl::updates::ReactiveVarStore,
|
||||
pub clear_pl_onclose: HashMap<String, String>,
|
||||
|
||||
pub rt_engine_config: EngineConfValues,
|
||||
pub config_parser: Rc<RefCell<ParseConfig>>,
|
||||
@@ -348,8 +356,19 @@ impl<B: DisplayBackend> App<B> {
|
||||
let output = format!("{:#?}", &self.pl_handler_store.read().unwrap());
|
||||
sender.send_success(output)?
|
||||
}
|
||||
DaemonCommand::TriggerUpdateUI { inject_vars, should_preserve_state, sender } => {
|
||||
match self.trigger_ui_update_with(inject_vars, should_preserve_state) {
|
||||
DaemonCommand::TriggerUpdateUI {
|
||||
inject_vars,
|
||||
should_preserve_state,
|
||||
lifetime,
|
||||
sender,
|
||||
} => {
|
||||
match self.trigger_ui_update_with(inject_vars, should_preserve_state, lifetime) {
|
||||
Ok(_) => sender.send_success(String::new())?,
|
||||
Err(e) => sender.send_failure(e.to_string())?,
|
||||
};
|
||||
}
|
||||
DaemonCommand::WidgetControl { action, sender } => {
|
||||
match self.perform_widget_control(action) {
|
||||
Ok(_) => sender.send_success(String::new())?,
|
||||
Err(e) => sender.send_failure(e.to_string())?,
|
||||
};
|
||||
@@ -404,6 +423,9 @@ impl<B: DisplayBackend> App<B> {
|
||||
|
||||
// let scope_index = ewwii_window.scope_index;
|
||||
ewwii_window.close();
|
||||
if let Some(var_name) = self.clear_pl_onclose.remove(instance_id) {
|
||||
self.pl_handler_store.write().unwrap().remove(&var_name);
|
||||
}
|
||||
|
||||
if auto_reopen {
|
||||
self.failed_windows.insert(instance_id.to_string());
|
||||
@@ -418,7 +440,7 @@ impl<B: DisplayBackend> App<B> {
|
||||
}
|
||||
|
||||
// stop poll/listen handlers if no windows are open
|
||||
if self.open_windows.is_empty() {
|
||||
if self.open_windows.is_empty() || self.reloading {
|
||||
rhai_impl::updates::kill_state_change_handler();
|
||||
}
|
||||
|
||||
@@ -504,7 +526,7 @@ impl<B: DisplayBackend> App<B> {
|
||||
}
|
||||
|
||||
let stored_parser_clone = self.config_parser.clone();
|
||||
if self.open_windows.is_empty() {
|
||||
if self.open_windows.is_empty() || self.reloading {
|
||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<String>();
|
||||
let widget_reg_store = self.widget_reg_store.clone();
|
||||
|
||||
@@ -518,6 +540,12 @@ impl<B: DisplayBackend> App<B> {
|
||||
let store = self.pl_handler_store.clone();
|
||||
let b_interval = self.rt_engine_config.batching_interval;
|
||||
|
||||
// kick start the localsignal
|
||||
rhai_impl::updates::handle_localsignal_changes(
|
||||
stored_parser_clone.clone(),
|
||||
compiled_ast.clone(),
|
||||
);
|
||||
|
||||
glib::MainContext::default().spawn_local(async move {
|
||||
let mut pending_updates = HashSet::new();
|
||||
|
||||
@@ -538,10 +566,11 @@ impl<B: DisplayBackend> App<B> {
|
||||
|
||||
let vars = store.read().unwrap().clone();
|
||||
let mut parser_rc = stored_parser_clone.borrow_mut();
|
||||
let compiled_ast_ref = compiled_ast.as_ref().map(|rc| rc.borrow());
|
||||
match generate_new_widgetnode(
|
||||
&vars,
|
||||
&config_path,
|
||||
compiled_ast.as_deref(),
|
||||
compiled_ast_ref.as_deref(),
|
||||
&mut *parser_rc,
|
||||
)
|
||||
.await
|
||||
@@ -572,13 +601,17 @@ impl<B: DisplayBackend> App<B> {
|
||||
let widget_reg_store = self.widget_reg_store.clone();
|
||||
let store = self.pl_handler_store.clone();
|
||||
|
||||
// notifiy localsignals
|
||||
rhai_impl::updates::notify_all_localsignals();
|
||||
|
||||
glib::MainContext::default().spawn_local(async move {
|
||||
let vars = store.read().unwrap().clone();
|
||||
let mut parser_rc = stored_parser_clone.borrow_mut();
|
||||
let compiled_ast_ref = compiled_ast.as_ref().map(|rc| rc.borrow());
|
||||
match generate_new_widgetnode(
|
||||
&vars,
|
||||
&config_path,
|
||||
compiled_ast.as_deref(),
|
||||
compiled_ast_ref.as_deref(),
|
||||
&mut *parser_rc,
|
||||
)
|
||||
.await
|
||||
@@ -696,27 +729,123 @@ impl<B: DisplayBackend> App<B> {
|
||||
log::info!("Reloading windows");
|
||||
log::trace!("loading config: {:#?}", config);
|
||||
|
||||
self.ewwii_config = config;
|
||||
self.reloading = true;
|
||||
let result = (|| -> Result<()> {
|
||||
self.ewwii_config.replace_data(config);
|
||||
|
||||
let open_window_ids: Vec<String> = self
|
||||
.open_windows
|
||||
.keys()
|
||||
.cloned()
|
||||
.chain(self.failed_windows.iter().cloned())
|
||||
.dedup()
|
||||
.collect();
|
||||
|
||||
for instance_id in &open_window_ids {
|
||||
let window_arguments = self.instance_id_to_args.get(instance_id).with_context(|| {
|
||||
format!("Cannot reopen window, initial parameters were not saved correctly for {instance_id}")
|
||||
})?;
|
||||
self.open_window(&window_arguments.clone())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})();
|
||||
self.reloading = false;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Load a given CSS string into the gtk css provider
|
||||
pub fn load_css(&mut self, _file_id: usize, css: &str) -> Result<()> {
|
||||
self.css_provider.load_from_data(&css);
|
||||
|
||||
let open_window_ids: Vec<String> = self
|
||||
.open_windows
|
||||
.keys()
|
||||
.cloned()
|
||||
.chain(self.failed_windows.iter().cloned())
|
||||
.dedup()
|
||||
.collect();
|
||||
for instance_id in &open_window_ids {
|
||||
let window_arguments = self.instance_id_to_args.get(instance_id).with_context(|| {
|
||||
format!("Cannot reopen window, initial parameters were not saved correctly for {instance_id}")
|
||||
})?;
|
||||
self.open_window(&window_arguments.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load a given CSS string into the gtk css provider, returning a nicely formatted [`DiagError`] when GTK errors out
|
||||
pub fn load_css(&mut self, _file_id: usize, css: &str) -> Result<()> {
|
||||
self.css_provider.load_from_data(&css);
|
||||
/// Perform widget control based on the action
|
||||
pub fn perform_widget_control(
|
||||
&mut self,
|
||||
action: crate::opts::WidgetControlAction,
|
||||
) -> Result<()> {
|
||||
match action {
|
||||
crate::opts::WidgetControlAction::Remove { names } => {
|
||||
if let Ok(mut maybe_registry) = self.widget_reg_store.lock() {
|
||||
if let Some(widget_registry) = maybe_registry.as_mut() {
|
||||
for name in names {
|
||||
widget_registry.remove_widget_by_name(&name);
|
||||
}
|
||||
} else {
|
||||
log::error!("Widget registry is empty");
|
||||
}
|
||||
} else {
|
||||
log::error!("Failed to acquire lock on widget registry");
|
||||
}
|
||||
}
|
||||
crate::opts::WidgetControlAction::Create { rhai_codes, parent_name } => {
|
||||
let mut parser = self.config_parser.borrow_mut();
|
||||
for rhai_code in rhai_codes {
|
||||
let widget_node = parser.eval_code_snippet(&rhai_code)?;
|
||||
let wid = rhai_impl::ast::hash_props(widget_node.props().ok_or_else(|| {
|
||||
anyhow::anyhow!("Failed to retreive the properties of this widget.")
|
||||
})?);
|
||||
|
||||
if let Ok(mut maybe_registry) = self.widget_reg_store.lock() {
|
||||
if let Some(widget_registry) = maybe_registry.as_mut() {
|
||||
let pid =
|
||||
widget_registry.get_widget_id_by_name(&parent_name).ok_or_else(
|
||||
|| anyhow::anyhow!("Widget '{}' not found", parent_name),
|
||||
)?;
|
||||
widget_registry.create_widget(&widget_node, wid, pid)?;
|
||||
} else {
|
||||
log::error!("Widget registry is empty");
|
||||
}
|
||||
} else {
|
||||
log::error!("Failed to acquire lock on widget registry");
|
||||
}
|
||||
}
|
||||
}
|
||||
crate::opts::WidgetControlAction::PropertyUpdate {
|
||||
property_and_value,
|
||||
widget_name,
|
||||
} => {
|
||||
if let Ok(mut maybe_registry) = self.widget_reg_store.lock() {
|
||||
if let Some(widget_registry) = maybe_registry.as_mut() {
|
||||
for (key, value) in &property_and_value {
|
||||
widget_registry.update_property_by_name(
|
||||
&widget_name,
|
||||
(key.clone(), value.clone()),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log::error!("Widget registry is empty");
|
||||
}
|
||||
} else {
|
||||
log::error!("Failed to acquire lock on widget registry");
|
||||
}
|
||||
}
|
||||
crate::opts::WidgetControlAction::AddClass { class, widget_name } => {
|
||||
if let Ok(mut maybe_registry) = self.widget_reg_store.lock() {
|
||||
if let Some(widget_registry) = maybe_registry.as_mut() {
|
||||
widget_registry.update_class_of_widget_by_name(&widget_name, &class, false);
|
||||
} else {
|
||||
log::error!("Widget registry is empty");
|
||||
}
|
||||
} else {
|
||||
log::error!("Failed to acquire lock on widget registry");
|
||||
}
|
||||
}
|
||||
crate::opts::WidgetControlAction::RemoveClass { class, widget_name } => {
|
||||
if let Ok(mut maybe_registry) = self.widget_reg_store.lock() {
|
||||
if let Some(widget_registry) = maybe_registry.as_mut() {
|
||||
widget_registry.update_class_of_widget_by_name(&widget_name, &class, true);
|
||||
} else {
|
||||
log::error!("Widget registry is empty");
|
||||
}
|
||||
} else {
|
||||
log::error!("Failed to acquire lock on widget registry");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -724,9 +853,10 @@ impl<B: DisplayBackend> App<B> {
|
||||
/// Trigger a UI update with the given flags.
|
||||
/// Even if there are no flags, the UI will still be updated.
|
||||
pub fn trigger_ui_update_with(
|
||||
&self,
|
||||
&mut self,
|
||||
inject_vars: Option<HashMap<String, String>>,
|
||||
should_preserve_state: bool,
|
||||
lifetime: Option<String>,
|
||||
) -> Result<()> {
|
||||
let compiled_ast = self.ewwii_config.get_owned_compiled_ast();
|
||||
let config_path = self.paths.get_rhai_path();
|
||||
@@ -749,6 +879,7 @@ impl<B: DisplayBackend> App<B> {
|
||||
if let Some(vars) = inject_vars {
|
||||
for (name, val) in vars {
|
||||
scope.set_value(name.clone(), Dynamic::from(val.clone()));
|
||||
let name_clone = name.clone();
|
||||
|
||||
// Preserving the new state.
|
||||
// ---
|
||||
@@ -756,14 +887,19 @@ impl<B: DisplayBackend> App<B> {
|
||||
// in the poll/listen variable store (or the `pl_handler_store` in self)
|
||||
if should_preserve_state {
|
||||
self.pl_handler_store.write().unwrap().insert(name, val);
|
||||
if let Some(win_name) = &lifetime {
|
||||
self.clear_pl_onclose.insert(win_name.clone(), name_clone);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let compiled_ast_ref = compiled_ast.as_ref().map(|rc| rc.borrow());
|
||||
|
||||
let new_root_widget = reeval_parser.eval_code_with(
|
||||
&rhai_code,
|
||||
Some(scope),
|
||||
compiled_ast.as_deref(),
|
||||
compiled_ast_ref.as_deref(),
|
||||
config_path.to_str(),
|
||||
)?;
|
||||
|
||||
@@ -789,9 +925,9 @@ impl<B: DisplayBackend> App<B> {
|
||||
|
||||
let mut scope = ParseConfig::initial_poll_listen_scope(&rhai_code)?;
|
||||
|
||||
// unwrap Rc<AST>
|
||||
// unwrap Rc<RefCell<AST>>
|
||||
let ast_ref: &rhai::AST =
|
||||
compiled_ast.as_ref().ok_or_else(|| anyhow!("AST not compiled yet"))?.as_ref();
|
||||
&*compiled_ast.as_ref().ok_or_else(|| anyhow!("AST not compiled yet"))?.borrow();
|
||||
|
||||
for fn_call in calls {
|
||||
reeval_parser.call_rhai_fn(ast_ref, &fn_call, Some(&mut scope))?;
|
||||
@@ -824,55 +960,70 @@ impl<B: DisplayBackend> App<B> {
|
||||
|
||||
unsafe {
|
||||
// Each plugin exposes: extern "C" fn create_plugin() -> Box<dyn Plugin>
|
||||
let constructor: libloading::Symbol<
|
||||
unsafe extern "C" fn() -> Box<dyn ewwii_plugin_api::Plugin>,
|
||||
> = lib
|
||||
.get(b"create_plugin")
|
||||
.map_err(|e| anyhow!("Failed to find create_plugin: {}", e))?;
|
||||
let constructor: libloading::Symbol<unsafe extern "C" fn() -> Box<dyn epapi::Plugin>> =
|
||||
lib.get(b"create_plugin")
|
||||
.map_err(|e| anyhow!("Failed to find create_plugin: {}", e))?;
|
||||
|
||||
let plugin = constructor(); // instantiate plugin
|
||||
let host = crate::plugin::EwwiiImpl { requestor: tx.clone() };
|
||||
plugin.init(&host); // call init immediately
|
||||
|
||||
set_active_plugin(lib)?; // keep library alive
|
||||
|
||||
let host = crate::plugin::EwwiiImpl { requestor: tx.clone() };
|
||||
plugin.init(&host); // call init immediately
|
||||
}
|
||||
|
||||
let cp = self.config_parser.clone();
|
||||
let wgs = self.widget_reg_store.clone();
|
||||
|
||||
glib::MainContext::default().spawn_local(async move {
|
||||
while let Ok(req) = rx.recv() {
|
||||
match req {
|
||||
PluginRequest::RhaiEngineAct(func) => {
|
||||
let mut cp = cp.borrow_mut();
|
||||
cp.action_with_engine(func);
|
||||
}
|
||||
PluginRequest::ListWidgetIds(res_tx) => {
|
||||
let wgs_guard = wgs.lock().unwrap();
|
||||
if let Some(wgs_brw) = wgs_guard.as_ref() {
|
||||
let output: Vec<u64> = wgs_brw.widgets.keys().cloned().collect();
|
||||
|
||||
if let Err(e) = res_tx.send(output) {
|
||||
log::error!("Failed to send window list to host: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
PluginRequest::WidgetRegistryAct(func) => {
|
||||
let mut wgs_guard = wgs.lock().unwrap();
|
||||
if let Some(ref mut registry) = *wgs_guard {
|
||||
let repr_map: HashMap<u64, &mut gtk4::Widget> = registry
|
||||
.widgets
|
||||
.iter_mut()
|
||||
.map(|(id, entry)| (*id, &mut entry.widget))
|
||||
.collect();
|
||||
|
||||
func(&mut ewwii_plugin_api::widget_backend::WidgetRegistryRepr {
|
||||
widgets: repr_map,
|
||||
});
|
||||
}
|
||||
}
|
||||
let handle_request = move |req: PluginRequest| match req {
|
||||
PluginRequest::RhaiEngineAct(func) => {
|
||||
func(&mut cp.borrow_mut().engine);
|
||||
}
|
||||
PluginRequest::RegisterFunc((name, namespace, func)) => match namespace {
|
||||
epapi::rhai_backend::RhaiFnNamespace::Custom(ns) => {
|
||||
let mut module = rhai::Module::new();
|
||||
module.set_native_fn(name, func);
|
||||
cp.borrow_mut().engine.register_static_module(&ns, module.into());
|
||||
}
|
||||
epapi::rhai_backend::RhaiFnNamespace::Global => {
|
||||
cp.borrow_mut().engine.register_fn(name, func);
|
||||
}
|
||||
},
|
||||
PluginRequest::ListWidgetIds(res_tx) => {
|
||||
let wgs_guard = wgs.lock().unwrap();
|
||||
if let Some(wgs_brw) = wgs_guard.as_ref() {
|
||||
let output: Vec<u64> = wgs_brw.widgets.keys().cloned().collect();
|
||||
let _ = res_tx.send(output);
|
||||
}
|
||||
}
|
||||
PluginRequest::WidgetRegistryAct(func) => {
|
||||
let mut wgs_guard = wgs.lock().unwrap();
|
||||
if let Some(ref mut registry) = *wgs_guard {
|
||||
let repr_map: HashMap<u64, &mut gtk4::Widget> = registry
|
||||
.widgets
|
||||
.iter_mut()
|
||||
.map(|(id, entry)| (*id, &mut entry.widget))
|
||||
.collect();
|
||||
|
||||
func(&mut ewwii_plugin_api::widget_backend::WidgetRegistryRepr {
|
||||
widgets: repr_map,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// quick drain
|
||||
while let Ok(req) = rx.try_recv() {
|
||||
handle_request(req);
|
||||
}
|
||||
|
||||
// handling requests that arrive later
|
||||
glib::MainContext::default().spawn_local(async move {
|
||||
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
while let Ok(req) = rx.recv() {
|
||||
handle_request(req);
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
Ok(())
|
||||
@@ -1027,6 +1178,10 @@ async fn generate_new_widgetnode(
|
||||
compiled_ast: Option<&rhai::AST>,
|
||||
parser: &mut ParseConfig,
|
||||
) -> Result<WidgetNode> {
|
||||
if !code_path.exists() {
|
||||
bail!("The configuration file `{}` does not exist", code_path.display());
|
||||
}
|
||||
|
||||
let rhai_code = parser.code_from_file(&code_path)?;
|
||||
|
||||
let mut scope = ParseConfig::initial_poll_listen_scope(&rhai_code)?;
|
||||
@@ -1034,10 +1189,6 @@ async fn generate_new_widgetnode(
|
||||
scope.set_value(name.clone(), Dynamic::from(val.clone()));
|
||||
}
|
||||
|
||||
if !code_path.exists() {
|
||||
bail!("The configuration file `{}` does not exist", code_path.display());
|
||||
}
|
||||
|
||||
let new_root_widget =
|
||||
parser.eval_code_with(&rhai_code, Some(scope), compiled_ast, code_path.to_str())?;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::{
|
||||
window::backend_window_options::BackendWindowOptionsDef,
|
||||
};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -27,7 +28,7 @@ pub fn read_from_ewwii_paths(
|
||||
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)]
|
||||
@@ -87,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))),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -109,7 +110,18 @@ impl EwwiiConfig {
|
||||
self.root_node.clone().ok_or_else(|| anyhow::anyhow!("root_node is missing"))
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ fn run<B: DisplayBackend>(opts: opts::Opt, ewwii_binary_name: String) -> Result<
|
||||
|
||||
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,
|
||||
};
|
||||
@@ -168,11 +168,11 @@ fn run<B: DisplayBackend>(opts: opts::Opt, ewwii_binary_name: String) -> Result<
|
||||
}
|
||||
|
||||
// make sure that there isn't already a Ewwii daemon running.
|
||||
opts::Action::Daemon if check_server_running(paths.get_ipc_socket_file()) => {
|
||||
opts::Action::Daemon { .. } if check_server_running(paths.get_ipc_socket_file()) => {
|
||||
eprintln!("Ewwii server already running.");
|
||||
true
|
||||
}
|
||||
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());
|
||||
|
||||
@@ -182,8 +182,12 @@ fn run<B: DisplayBackend>(opts: opts::Opt, ewwii_binary_name: String) -> Result<
|
||||
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
|
||||
}
|
||||
|
||||
@@ -223,7 +227,7 @@ fn run<B: DisplayBackend>(opts: opts::Opt, ewwii_binary_name: String) -> Result<
|
||||
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);
|
||||
|
||||
@@ -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),
|
||||
@@ -184,6 +187,13 @@ 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 {
|
||||
@@ -194,9 +204,13 @@ pub enum ActionWithServer {
|
||||
#[arg(long = "inject", short = 'i', value_parser = parse_inject_var_map)]
|
||||
inject_vars: Option<HashMap<String, String>>,
|
||||
|
||||
/// Preserve the new updates. Only meaningful if used with inject.
|
||||
/// 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)
|
||||
@@ -227,6 +241,59 @@ pub enum ActionWithServer {
|
||||
},
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
pub fn from_env() -> Self {
|
||||
let raw: RawOpt = RawOpt::parse();
|
||||
@@ -286,10 +353,17 @@ impl ActionWithServer {
|
||||
self,
|
||||
) -> (app::DaemonCommand, Option<daemon_response::DaemonResponseReceiver>) {
|
||||
let command = match self {
|
||||
ActionWithServer::TriggerUpdateUI { inject_vars, should_preserve_state } => {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ewwii_plugin_api::{widget_backend, EwwiiAPI};
|
||||
use rhai::Engine;
|
||||
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 {
|
||||
@@ -33,6 +33,20 @@ impl EwwiiAPI for EwwiiImpl {
|
||||
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();
|
||||
@@ -60,6 +74,13 @@ impl EwwiiAPI for EwwiiImpl {
|
||||
|
||||
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>),
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||
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();
|
||||
|
||||
@@ -36,23 +37,6 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||
|
||||
let config_parser =
|
||||
Rc::new(RefCell::new(rhai_impl::parser::ParseConfig::new(Some(pl_handler_store.clone()))));
|
||||
let mut config_parser_mut = config_parser.borrow_mut();
|
||||
|
||||
let read_config = config::read_from_ewwii_paths(&paths, &mut *config_parser_mut);
|
||||
|
||||
// free the temporary parser borrow
|
||||
drop(config_parser_mut);
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
cleanup_log_dir(paths.get_log_dir())?;
|
||||
|
||||
@@ -92,15 +76,17 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||
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: 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,
|
||||
clear_pl_onclose: HashMap::new(),
|
||||
rt_engine_config: EngineConfValues::default(),
|
||||
config_parser,
|
||||
paths,
|
||||
@@ -108,12 +94,33 @@ pub fn initialize_server<B: DisplayBackend>(
|
||||
phantom: PhantomData,
|
||||
};
|
||||
|
||||
// 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,
|
||||
gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||
);
|
||||
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()) {
|
||||
|
||||
@@ -41,21 +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::FlowBox { props, children } => {
|
||||
build_gtk_flowbox(props, children, widget_reg)?.upcast()
|
||||
}
|
||||
WidgetNode::EventBox { props, children } => {
|
||||
build_event_box(props, children, widget_reg)?.upcast()
|
||||
}
|
||||
WidgetNode::ToolTip { props, children } => {
|
||||
build_tooltip(props, children, widget_reg)?.upcast()
|
||||
}
|
||||
// WidgetNode::CircularProgress { props } => {
|
||||
// build_circular_progress_bar(props, 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::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::Slider { props } => build_gtk_scale(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_image(props, widget_reg)?.upcast(),
|
||||
WidgetNode::Icon { props } => build_icon(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,273 +1,199 @@
|
||||
// use anyhow::{anyhow, Result};
|
||||
// use gtk4::glib::{self, object_subclass, prelude::*, wrapper, Properties};
|
||||
// use gtk4::{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 gtk4::Bin, gtk4::Container, gtk4::Widget;
|
||||
// }
|
||||
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>,
|
||||
}
|
||||
|
||||
// #[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>,
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #[property(
|
||||
// get,
|
||||
// set,
|
||||
// nick = "Value",
|
||||
// blurb = "The value",
|
||||
// minimum = 0f64,
|
||||
// maximum = 100f64,
|
||||
// default = 0f64
|
||||
// )]
|
||||
// value: RefCell<f64>,
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for CircProg {
|
||||
const NAME: &'static str = "CircProg";
|
||||
type Type = super::CircProg;
|
||||
type ParentType = gtk4::Widget;
|
||||
}
|
||||
|
||||
// #[property(
|
||||
// get,
|
||||
// set,
|
||||
// nick = "Thickness",
|
||||
// blurb = "Thickness",
|
||||
// minimum = 0f64,
|
||||
// maximum = 100f64,
|
||||
// default = 1f64
|
||||
// )]
|
||||
// thickness: RefCell<f64>,
|
||||
impl ObjectImpl for CircProg {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
// #[property(get, set, nick = "Clockwise", blurb = "Clockwise", default = true)]
|
||||
// clockwise: RefCell<bool>,
|
||||
let obj = self.obj();
|
||||
obj.add_css_class("circular-progress");
|
||||
}
|
||||
|
||||
// content: RefCell<Option<gtk4::Widget>>,
|
||||
// }
|
||||
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()
|
||||
}
|
||||
|
||||
// // 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),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
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,),
|
||||
}
|
||||
self.obj().queue_draw();
|
||||
}
|
||||
|
||||
// impl ObjectImpl for CircProgPriv {
|
||||
// fn properties() -> &'static [glib::ParamSpec] {
|
||||
// Self::derived_properties()
|
||||
// }
|
||||
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,),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// }
|
||||
// "thickness" => {
|
||||
// self.thickness.replace(value.get().unwrap());
|
||||
// }
|
||||
// "start-at" => {
|
||||
// self.start_at.replace(value.get().unwrap());
|
||||
// }
|
||||
// "clockwise" => {
|
||||
// self.clockwise.replace(value.get().unwrap());
|
||||
// }
|
||||
// x => panic!("Tried to set inexistant property of CircProg: {}", x,),
|
||||
// }
|
||||
// }
|
||||
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)
|
||||
}
|
||||
|
||||
// fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
// self.derived_property(id, pspec)
|
||||
// }
|
||||
// }
|
||||
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();
|
||||
|
||||
// #[object_subclass]
|
||||
// impl ObjectSubclass for CircProgPriv {
|
||||
// type ParentType = gtk4::Bin;
|
||||
// type Type = CircProg;
|
||||
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
|
||||
|
||||
// const NAME: &'static str = "CircProg";
|
||||
let (start_angle, end_angle) = if clockwise {
|
||||
(0.0, perc_to_rad(value))
|
||||
} else {
|
||||
(perc_to_rad(100.0 - value), 2f64 * std::f64::consts::PI)
|
||||
};
|
||||
|
||||
// fn class_init(klass: &mut Self::Class) {
|
||||
// klass.set_css_name("circular-progress");
|
||||
// }
|
||||
// }
|
||||
let total_width = self.obj().allocated_width() as f64;
|
||||
let total_height = self.obj().allocated_height() as f64;
|
||||
let center = (total_width / 2.0, total_height / 2.0);
|
||||
|
||||
// impl Default for CircProg {
|
||||
// fn default() -> Self {
|
||||
// Self::new()
|
||||
// }
|
||||
// }
|
||||
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;
|
||||
|
||||
// impl CircProg {
|
||||
// pub fn new() -> Self {
|
||||
// glib::Object::new::<Self>()
|
||||
// }
|
||||
// }
|
||||
// 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,
|
||||
));
|
||||
|
||||
// impl ContainerImpl for CircProgPriv {
|
||||
// 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()));
|
||||
// }
|
||||
// }
|
||||
cr.save().unwrap();
|
||||
|
||||
// fn calc_widget_lowest_preferred_dimension(widget: >k4::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)
|
||||
// }
|
||||
// Centering
|
||||
cr.translate(center.0, center.1);
|
||||
cr.rotate(perc_to_rad(start_at));
|
||||
cr.translate(-center.0, -center.1);
|
||||
|
||||
// impl BinImpl 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().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().unwrap();
|
||||
|
||||
// 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(gtk4::StateFlags::NORMAL);
|
||||
// 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().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().unwrap();
|
||||
|
||||
// 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)
|
||||
// }
|
||||
// }
|
||||
cr.restore().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fn preferred_width_for_height(&self, _height: i32) -> (i32, i32) {
|
||||
// self.preferred_width()
|
||||
// }
|
||||
glib::wrapper! {
|
||||
pub struct CircProg(ObjectSubclass<imp::CircProg>)
|
||||
@extends gtk4::Widget,
|
||||
@implements gtk4::Accessible, gtk4::Actionable, gtk4::Buildable, gtk4::ConstraintTarget;
|
||||
}
|
||||
|
||||
// fn preferred_height(&self) -> (i32, i32) {
|
||||
// let styles = self.obj().style_context();
|
||||
// let margin = styles.margin(gtk4::StateFlags::NORMAL);
|
||||
impl CircProg {
|
||||
pub fn new() -> Self {
|
||||
Object::builder().build()
|
||||
}
|
||||
}
|
||||
|
||||
// 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(gtk4::StateFlags::NORMAL);
|
||||
// // Padding is not supported yet
|
||||
// let fg_color: gdk::RGBA = styles.color(gtk4::StateFlags::NORMAL);
|
||||
// let bg_color: gdk::RGBA = styles
|
||||
// .style_property_for_state("background-color", gtk4::StateFlags::NORMAL)
|
||||
// .get()?;
|
||||
// let (start_angle, end_angle) = if clockwise {
|
||||
// (0.0, perc_to_rad(value))
|
||||
// } else {
|
||||
// (perc_to_rad(100.0 - value), 2f64 * std::f64::consts::PI)
|
||||
// };
|
||||
|
||||
// let total_width = self.obj().allocated_width() as f64;
|
||||
// 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 outer_ring = f64::min(circle_width, circle_height) / 2.0;
|
||||
// let inner_ring = (f64::min(circle_width, circle_height) / 2.0) - thickness;
|
||||
|
||||
// cr.save()?;
|
||||
|
||||
// // Centering
|
||||
// cr.translate(center.0, center.1);
|
||||
// cr.rotate(perc_to_rad(start_at));
|
||||
// cr.translate(-center.0, -center.1);
|
||||
|
||||
// // 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.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()?;
|
||||
|
||||
// // 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.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()?;
|
||||
|
||||
// // Draw the children widget, clipping it to the inside
|
||||
// if let Some(child) = &*self.content.borrow() {
|
||||
// cr.save()?;
|
||||
|
||||
// // 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();
|
||||
|
||||
// // 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
|
||||
// }
|
||||
// }
|
||||
|
||||
// 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,5 +1,10 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
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;
|
||||
|
||||
@@ -159,6 +164,17 @@ pub(super) fn parse_position_type(s: &str) -> Result<gtk4::PositionType> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper of helpers
|
||||
fn replace_placeholders<T>(cmd: &str, args: &[T]) -> String
|
||||
where
|
||||
@@ -215,3 +231,105 @@ pub(super) fn parse_stack_transition(t: &str) -> Result<gtk4::StackTransitionTyp
|
||||
_ => 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,6 +1,6 @@
|
||||
[package]
|
||||
name = "ewwii_plugin_api"
|
||||
version = "0.4.0"
|
||||
version = "0.7.0"
|
||||
authors = ["byson94 <byson94wastaken@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -18,5 +18,6 @@ include-gtk4 = ["dep:gtk4"]
|
||||
include-rhai = ["dep:rhai"]
|
||||
|
||||
[dependencies]
|
||||
rhai = { workspace = true, optional = true }
|
||||
# rhai crate features should exactly match that of rhai_impl
|
||||
rhai = { workspace = true, optional = true, features = ["internals"] }
|
||||
gtk4 = { workspace = true, optional = true }
|
||||
@@ -9,58 +9,32 @@
|
||||
/// The following example shows how you can use this macro to
|
||||
/// easily make plugins in a single step.
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// use ewwii_plugin_api::auto_plugin;
|
||||
///
|
||||
/// auto_plugin!(MyPluginName, {
|
||||
/// // host variable is passed in automatically
|
||||
/// host.log("Easy, huh?");
|
||||
/// })
|
||||
///
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// That's it! The plugin is ready.
|
||||
///
|
||||
/// ## When not to use it
|
||||
///
|
||||
/// This macro shall not be used if you want to have
|
||||
/// fields in your plugin.
|
||||
///
|
||||
/// ```rust
|
||||
/// struct MyPluginName {
|
||||
/// awesome_field: String
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// For a structure like the above, you should do this instead:
|
||||
///
|
||||
/// ```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);
|
||||
/// ```
|
||||
/// 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) {
|
||||
impl $crate::Plugin for $struct_name {
|
||||
fn init(&self, host: &dyn $crate::EwwiiAPI) {
|
||||
$init_block
|
||||
}
|
||||
}
|
||||
|
||||
export_plugin!($struct_name);
|
||||
$crate::export_plugin!($struct_name);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
//! # 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 is a **must-have** for building plugins for ewwii
|
||||
//! as this explicit layout is what ewwii requires a plugin to have.
|
||||
//! This crate simplifies and provides a safe way for building
|
||||
//! plugins for ewwii.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
@@ -27,10 +29,14 @@
|
||||
mod export_macros;
|
||||
|
||||
pub mod example;
|
||||
pub mod rhai_backend;
|
||||
pub mod widget_backend;
|
||||
|
||||
#[cfg(feature = "include-rhai")]
|
||||
use rhai::Engine;
|
||||
pub use rhai;
|
||||
|
||||
#[cfg(feature = "include-gtk4")]
|
||||
pub use gtk4;
|
||||
|
||||
/// The shared trait defining the Ewwii plugin API
|
||||
pub trait EwwiiAPI: Send + Sync {
|
||||
@@ -48,15 +54,76 @@ pub trait EwwiiAPI: Send + Sync {
|
||||
/// _(include-rhai)_ Perform actions on the latest rhai engine.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
///
|
||||
/// ```rust
|
||||
/// host.rhai_engine_action(Box::new(|eng| {
|
||||
/// // eng = rhai::Engine
|
||||
/// eng.set_max_expr_depths(128, 128);
|
||||
/// }));
|
||||
/// 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 Engine) + Send>) -> Result<(), String>;
|
||||
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
|
||||
@@ -65,12 +132,20 @@ pub trait EwwiiAPI: Send + Sync {
|
||||
/// _(include-gtk4)_ Perform actions on the latest widget registry.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
///
|
||||
/// ```rust
|
||||
/// host.widget_reg_action(Box::new(|wrg| {
|
||||
/// // wrg = widget_backend::WidgetRegistryRepr
|
||||
/// // The gtk4::Widget can be modified here.
|
||||
/// }));
|
||||
/// 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(
|
||||
@@ -86,12 +161,14 @@ pub trait EwwiiAPI: Send + Sync {
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use ewwii_plugin_api::{Plugin, export_plugin};
|
||||
/// use ewwii_plugin_api::{Plugin, EwwiiAPI, export_plugin};
|
||||
///
|
||||
/// struct MyStruct;
|
||||
///
|
||||
/// sturct MyStruct;
|
||||
///
|
||||
/// impl Plugin for MyStruct {
|
||||
/// /* Implementation Skipped */
|
||||
/// fn init(&self, host: &dyn EwwiiAPI) {
|
||||
/// /* Implementation Skipped */
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Automatically does all the FFI related exports
|
||||
|
||||
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::*;
|
||||
@@ -18,4 +18,4 @@ mod gtk4_included {
|
||||
}
|
||||
|
||||
#[cfg(feature = "include-gtk4")]
|
||||
pub use gtk4_included::*;
|
||||
pub use gtk4_included::*;
|
||||
|
||||
@@ -10,6 +10,7 @@ homepage = "https://github.com/byson94/ewwii"
|
||||
|
||||
[dependencies]
|
||||
shared_utils.workspace = true
|
||||
scan_prop_proc.workspace = true
|
||||
|
||||
rhai = { workspace = true, features = ["internals"] }
|
||||
anyhow.workspace = true
|
||||
@@ -21,6 +22,7 @@ ahash.workspace = true
|
||||
nix = { workspace = true, features = ["process", "fs", "signal"] }
|
||||
libc.workspace = true
|
||||
# error handling
|
||||
rhai_trace = "0.3.0"
|
||||
rhai_trace = "0.3.1"
|
||||
codespan-reporting.workspace = true
|
||||
regex.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> },
|
||||
FlowBox { props: Map, children: Vec<WidgetNode> },
|
||||
Button { props: Map },
|
||||
Image { props: Map },
|
||||
Icon { 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,6 +79,13 @@ pub fn get_id_to_widget_info<'a>(
|
||||
get_id_to_widget_info(child, id_to_props, Some(id))?;
|
||||
}
|
||||
}
|
||||
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))?;
|
||||
}
|
||||
}
|
||||
WidgetNode::EventBox { props, children } => {
|
||||
let id = hash_props_and_type(props, "EventBox");
|
||||
insert_wdgt_info(node, props, "EventBox", children.as_slice(), parent_id, id_to_props)?;
|
||||
@@ -91,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");
|
||||
@@ -103,10 +117,6 @@ pub fn get_id_to_widget_info<'a>(
|
||||
// let id = hash_props_and_type(props, "Image");
|
||||
insert_wdgt_info(node, props, "Image", &[], parent_id, id_to_props)?;
|
||||
}
|
||||
WidgetNode::Icon { props } => {
|
||||
// let id = hash_props_and_type(props, "Icon");
|
||||
insert_wdgt_info(node, props, "Icon", &[], parent_id, id_to_props)?;
|
||||
}
|
||||
WidgetNode::Button { props } => {
|
||||
// let id = hash_props_and_type(props, "Button");
|
||||
insert_wdgt_info(node, props, "Button", &[], parent_id, id_to_props)?;
|
||||
@@ -141,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)?;
|
||||
@@ -217,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 {
|
||||
@@ -37,11 +44,10 @@ pub fn register_all_widgets(engine: &mut Engine, all_nodes: &Rc<RefCell<Vec<Widg
|
||||
register_primitive!("label", Label);
|
||||
register_primitive!("button", Button);
|
||||
register_primitive!("image", Image);
|
||||
register_primitive!("icon", Icon);
|
||||
register_primitive!("input", Input);
|
||||
register_primitive!("progress", Progress);
|
||||
register_primitive!("combo_box_text", ComboBoxText);
|
||||
register_primitive!("scale", Slider);
|
||||
register_primitive!("scale", Scale);
|
||||
register_primitive!("checkbox", Checkbox);
|
||||
register_primitive!("calendar", Calendar);
|
||||
register_primitive!("graph", Graph);
|
||||
@@ -67,6 +73,7 @@ pub fn register_all_widgets(engine: &mut Engine, all_nodes: &Rc<RefCell<Vec<Widg
|
||||
}
|
||||
|
||||
register_with_children!("box", Box);
|
||||
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(
|
||||
|
||||
@@ -39,6 +39,10 @@ impl WidgetNode {
|
||||
props: with_dyn_id(props.clone(), parent_path),
|
||||
children: process_children(children, parent_path, "box"),
|
||||
},
|
||||
WidgetNode::FlowBox { props, children } => WidgetNode::FlowBox {
|
||||
props: with_dyn_id(props.clone(), parent_path),
|
||||
children: process_children(children, parent_path, "flowbox"),
|
||||
},
|
||||
WidgetNode::Expander { props, children } => WidgetNode::Expander {
|
||||
props: with_dyn_id(props.clone(), parent_path),
|
||||
children: process_children(children, parent_path, "expander"),
|
||||
@@ -67,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) => {
|
||||
@@ -87,30 +99,29 @@ impl WidgetNode {
|
||||
node @ WidgetNode::Label { props }
|
||||
| node @ WidgetNode::Button { props }
|
||||
| node @ WidgetNode::Image { props }
|
||||
| node @ WidgetNode::Icon { props }
|
||||
| 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 {
|
||||
WidgetNode::Label { .. } => WidgetNode::Label { props: new_props },
|
||||
WidgetNode::Button { .. } => WidgetNode::Button { props: new_props },
|
||||
WidgetNode::Image { .. } => WidgetNode::Image { props: new_props },
|
||||
WidgetNode::Icon { .. } => WidgetNode::Icon { props: new_props },
|
||||
WidgetNode::Input { .. } => WidgetNode::Input { props: new_props },
|
||||
WidgetNode::Progress { .. } => WidgetNode::Progress { props: new_props },
|
||||
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 },
|
||||
@@ -121,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!(),
|
||||
}
|
||||
|
||||
@@ -11,9 +11,15 @@ pub fn format_eval_error(
|
||||
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,
|
||||
@@ -24,8 +30,14 @@ pub fn format_eval_error(
|
||||
|
||||
/// Return a formatted Rhai parse error.
|
||||
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,
|
||||
@@ -52,12 +64,15 @@ pub fn format_codespan_error(be: BetterError, code: &str, file_id: Option<&str>)
|
||||
}
|
||||
|
||||
// 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) => {
|
||||
@@ -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,6 +1,7 @@
|
||||
use crate::error::{format_eval_error, format_parse_error};
|
||||
use crate::parser::ParseConfig;
|
||||
use crate::updates::ReactiveVarStore;
|
||||
use rhai::Scope;
|
||||
use rhai::{Dynamic, Engine, EvalAltResult, Module, ModuleResolver, Position, AST};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
@@ -55,16 +56,31 @@ impl ModuleResolver for SimpleFileResolver {
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut scope = ParseConfig::initial_poll_listen_scope(&script).map_err(|e| {
|
||||
EvalAltResult::ErrorSystem(
|
||||
format!("error setting up default variables: {full_path:?}"),
|
||||
e.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) => {
|
||||
println!("VALBEF: {:#?}", val);
|
||||
let name_to_val: &HashMap<String, String> = &*val.read().unwrap();
|
||||
|
||||
for (name, val) in name_to_val {
|
||||
|
||||
@@ -8,36 +8,42 @@ use crate::{
|
||||
updates::ReactiveVarStore,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use rhai::{Dynamic, Engine, OptimizationLevel, 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(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 { pl_handler_store: pl_handler_store.clone() });
|
||||
|
||||
register_all_widgets(&mut engine, &all_nodes);
|
||||
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 compile_code(&mut self, code: &str, file_path: &str) -> Result<AST> {
|
||||
self.engine
|
||||
let mut ast = self
|
||||
.engine
|
||||
.compile(code)
|
||||
.map_err(|e| anyhow!(format_parse_error(&e, code, Some(file_path))))
|
||||
.map_err(|e| anyhow!(format_parse_error(&e, code, Some(file_path))))?;
|
||||
|
||||
ast.set_source(ImmutableString::from(file_path));
|
||||
Ok(ast)
|
||||
}
|
||||
|
||||
pub fn eval_code_with(
|
||||
@@ -65,6 +71,9 @@ impl ParseConfig {
|
||||
.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();
|
||||
@@ -86,6 +95,24 @@ impl ParseConfig {
|
||||
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> {
|
||||
Ok(fs::read_to_string(&file_path)
|
||||
.map_err(|e| anyhow!("Failed to read {:?}: {}", file_path.as_ref(), e))?)
|
||||
|
||||
@@ -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)
|
||||
|
||||
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,19 +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>,
|
||||
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());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -10,5 +10,6 @@ homepage = "https://github.com/ewwii-sh/ewwii"
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
rhai.workspace = true
|
||||
anyhow.workspace = true
|
||||
rhai = { workspace = true, features = ["internals"] }
|
||||
anyhow.workspace = true
|
||||
once_cell.workspace = true
|
||||
20
examples/activateLinux/ewwii.rhai
Normal file
20
examples/activateLinux/ewwii.rhai
Normal file
@@ -0,0 +1,20 @@
|
||||
enter([
|
||||
defwindow(
|
||||
"activate linux",
|
||||
#{
|
||||
monitor: 0,
|
||||
focusable: "none",
|
||||
stacking: "overlay",
|
||||
wm_ignore: false,
|
||||
geometry: #{
|
||||
x: "50px",
|
||||
y: "20px",
|
||||
width: "50px",
|
||||
height: "30px",
|
||||
anchor: "bottom right",
|
||||
},
|
||||
reserve: #{ distance: "40px", side: "top" },
|
||||
},
|
||||
label(#{ markup: "<big>Activate linux</big>\nGo to Settings to activate Linux", justify: "left",class: "activate"})
|
||||
),
|
||||
]);
|
||||
9
examples/activateLinux/ewwii.scss
Normal file
9
examples/activateLinux/ewwii.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
* {
|
||||
all: unset;
|
||||
}
|
||||
|
||||
|
||||
.activate {
|
||||
background: transparent;
|
||||
color: gray;
|
||||
}
|
||||
@@ -66,7 +66,7 @@ fn metric(props) {
|
||||
space_evenly: false,
|
||||
}, [
|
||||
box(#{ class: "label" }, [ label(#{ text: label_prop }) ]),
|
||||
slider(#{
|
||||
scale(#{
|
||||
min: 0,
|
||||
max: 101,
|
||||
active: onchange_prop != "",
|
||||
|
||||
12
flake.lock
generated
12
flake.lock
generated
@@ -18,11 +18,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1725534445,
|
||||
"narHash": "sha256-Yd0FK9SkWy+ZPuNqUgmVPXokxDgMJoGuNpMEtkfcf84=",
|
||||
"lastModified": 1768569498,
|
||||
"narHash": "sha256-bB6Nt99Cj8Nu5nIUq0GLmpiErIT5KFshMQJGMZwgqUo=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9bb1e7571aadf31ddb4af77fc64b2d59580f9a39",
|
||||
"rev": "be5afa0fcb31f0a96bf9ecba05a516c66fcd8114",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -46,11 +46,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1725675754,
|
||||
"narHash": "sha256-hXW3csqePOcF2e/PYnpXj72KEYyNj2HzTrVNmS/F7Ug=",
|
||||
"lastModified": 1768704795,
|
||||
"narHash": "sha256-Y33TAp2BHEcuspYvcmBXXD0qdvjftv73PwyKTDOjoSY=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "8cc45e678e914a16c8e224c3237fb07cf21e5e54",
|
||||
"rev": "4b7472a78857ac789fb26616040f55cfcbd36c6e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -56,13 +56,12 @@
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
pkg-config
|
||||
wrapGAppsHook
|
||||
wrapGAppsHook4
|
||||
];
|
||||
buildInputs = with pkgs; [
|
||||
gtk3
|
||||
gtk4
|
||||
librsvg
|
||||
gtk-layer-shell
|
||||
libdbusmenu-gtk3
|
||||
gtk4-layer-shell
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
17
proc_macros/scan_prop_proc/Cargo.toml
Normal file
17
proc_macros/scan_prop_proc/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "scan_prop_proc"
|
||||
version = "0.1.0"
|
||||
authors = ["byson94 <byson94wastaken@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
description = "A procedual macro for generating properties on a WidgetNode"
|
||||
repository = "https://github.com/byson94/ewwii"
|
||||
homepage = "https://github.com/byson94/ewwii"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn.workspace = true
|
||||
quote.workspace = true
|
||||
proc-macro2.workspace = true
|
||||
47
proc_macros/scan_prop_proc/src/lib.rs
Normal file
47
proc_macros/scan_prop_proc/src/lib.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, Data, DeriveInput, Fields};
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn scan_prop(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(item as DeriveInput);
|
||||
let name = &input.ident;
|
||||
|
||||
let props_matches = if let Data::Enum(data_enum) = &input.data {
|
||||
data_enum
|
||||
.variants
|
||||
.iter()
|
||||
.filter_map(|v| match &v.fields {
|
||||
Fields::Named(fields) => {
|
||||
for f in &fields.named {
|
||||
if f.ident.as_ref().map(|id| id == "props").unwrap_or(false) {
|
||||
let vname = &v.ident;
|
||||
return Some(quote! {
|
||||
#name::#vname { props, .. } => Some(props)
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let expanded = quote! {
|
||||
#input
|
||||
|
||||
impl #name {
|
||||
pub fn props(&self) -> Option<&Map> {
|
||||
match self {
|
||||
#(#props_matches),*,
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
@@ -5,5 +5,5 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
rhai_impl.workspace = true
|
||||
rhai = "1.22.2"
|
||||
rhai.workspace = true
|
||||
rhai-autodocs = "0.9.0"
|
||||
|
||||
Reference in New Issue
Block a user