689 Commits

Author SHA1 Message Date
Byson94
df910786a6 chore: minor rebranding 2026-01-10 11:13:36 +05:30
Byson94
d902343bb4 Merge pull request #18 from buzz/feat/graph
feat: add support for graph widget
2026-01-10 11:13:05 +05:30
Byson94
ecb85dd5bf Merge branch 'iidev' into feat/graph 2026-01-10 11:12:32 +05:30
Byson94
e4ab87bc2a feat: touch support to scale widget 2026-01-04 15:26:56 +05:30
Byson94
37b57aee60 feat: implement orientation for scale widget 2026-01-02 13:08:53 +05:30
Byson94
33ef1720e3 feat: return data early if mutations is empty 2026-01-01 12:55:40 +05:30
Byson94
c41a495ee0 localsignal: fix no compiled ast warning returning early 2026-01-01 12:49:39 +05:30
Byson94
47f93e9cab chore: run cargo fmt 2026-01-01 12:46:56 +05:30
Byson94
9e91ae61a5 Merge pull request #19 from BinaryHarbinger/main
chore: Change source of Binarydots example
2026-01-01 11:06:58 +05:30
BinaryHarbinger
87cc157055 chore: Change source of Binarydots example 2025-12-31 20:43:19 +03:00
Byson94
29983ab9da feat: add eval_ignore prop to all widgets 2025-12-30 09:25:00 +05:30
Byson94
ff9db50831 feat: add mutations property to localsignal 2025-12-28 19:41:01 +05:30
Byson94
4cb6ac05d3 docs: remove unnecessary feat in changelog 2025-12-28 14:25:55 +05:30
Byson94
73a64944b8 docs(changelog): fix removed section in unreleased 2025-12-25 10:54:16 +05:30
Byson94
d25c2db420 chore: cargo fmt 2025-12-24 11:19:09 +05:30
Byson94
216775f55a feat(img wdgt): custom rendering + new feats 2025-12-24 11:18:33 +05:30
Byson94
ad79e81c50 feat: new image widget features & fully remove icon 2025-12-23 14:57:52 +05:30
Byson94
df7226d06c feat: remove icon widget 2025-12-23 14:50:16 +05:30
Byson94
6e03473133 feat: replace image widget rendering 2025-12-22 19:17:27 +05:30
Byson94
36c58e211d feat: update props of icon widget 2025-12-22 18:18:26 +05:30
buzz
2335ca5fe5 feat: add support for graph widget 2025-12-20 11:37:00 +01:00
buzz
8865fc0f6f chore: add v4_18 to gtk4 crate
Enable more recent Gtk APIs like `gsk::PathBuilder` and `gtk::Widget::color()`.
2025-12-20 10:55:16 +01:00
buzz
f6eec1fe51 feat: add skip_unchanged property to poll 2025-12-13 11:24:30 +01:00
Byson94
70de347bcf fix: clockwise prop not working on circ progress 2025-12-10 14:52:56 +05:30
Byson94
2cbf64e250 feat: add text and show_text prop to progressbar widget 2025-12-09 18:34:44 +05:30
Byson94
a14e559c80 feat: rename WidgetNode::Slider to WidgetNode::Scale 2025-12-09 17:04:32 +05:30
Byson94
59fb1b85eb feat: remove ewwii_anims from cargo.toml 2025-12-08 21:32:06 +05:30
Byson94
4197a863e5 feat: change doccomment description of rhai_impl crate 2025-12-08 19:49:09 +05:30
Byson94
4293c6877d feat: migrate most legacycontroller to gestureclick
This provides touch support to widgets
2025-12-07 14:14:35 +05:30
Byson94
f393627932 feat: parse widget_action ucontainer actions like shell 2025-12-06 16:14:53 +05:30
Byson94
c54ce27505 feat: add features section in readme 2025-12-02 19:01:32 +05:30
Byson94
97518eb49c chore: run cargo fmt 2025-11-29 20:45:31 +05:30
Byson94
ddce15481f feat: remove remove action from widget_action utility 2025-11-29 19:49:52 +05:30
Byson94
43721426e8 feat: proptotype widgetaction utility widget 2025-11-28 20:25:18 +05:30
Byson94
6baa9c7858 feat: add transition_duration prop to stack widget 2025-11-26 20:18:28 +05:30
Byson94
8ec080a290 feat: add add/remove-class subcommand to wc command 2025-11-26 19:15:28 +05:30
Byson94
166c440978 chore: cargo fmt 2025-11-25 21:05:24 +05:30
Byson94
6e9dca9d42 feat: add placeholder property to input widget 2025-11-25 21:04:32 +05:30
Byson94
b50f41b1e0 feat: add propety-update argument to widget control command 2025-11-25 20:49:26 +05:30
Byson94
357dcaacbc fix: doc comment of epapi register_function 2025-11-24 19:33:49 +05:30
Byson94
61e681e6bd chore: run cargo fmt 2025-11-24 19:26:30 +05:30
Byson94
ebd4264621 fix: localbind not finding properties of range subclasses 2025-11-23 17:49:22 +05:30
Byson94
4167c64fde feat: fix register_function docs in epapi 2025-11-22 17:41:24 +05:30
Byson94
6456b2998d feat: add link to documentation in readme 2025-11-22 17:39:16 +05:30
Byson94
4de5ab3c59 chore: commit change in cargo.lock 2025-11-22 12:08:07 +05:30
Byson94
04ca79a5af feat: improve epapi; bump epapi to 0.7.0; 2025-11-22 12:05:30 +05:30
Byson94
0d803fd962 chore: run cargo fmt 2025-11-22 11:13:24 +05:30
Byson94
8406c86117 feat: bump ewwii_plugin_api version 2025-11-21 21:53:41 +05:30
Byson94
1aee3163e0 feat: make register_function on ewwii_plugin_api register directly 2025-11-21 21:53:19 +05:30
Byson94
9aec974e9e feat: add warning on ewwii_plugin_api until it is fixed 2025-11-21 18:06:52 +05:30
Byson94
4de148be58 feat: remove debug in action_with_engine 2025-11-20 21:02:23 +05:30
Byson94
3f48178333 feat: matching rhai features across all crates 2025-11-19 14:35:55 +05:30
Byson94
8af01e44f2 feat: improve plugin loading order 2025-11-17 20:05:26 +05:30
Byson94
a76440f242 feat: add trace log in action_with_engine 2025-11-16 20:29:14 +05:30
Byson94
49c0d14ace feat: update wc actions to take vec of strings 2025-11-13 19:27:46 +05:30
Byson94
6a6856192c feat: make wc add eval rhai code instead of .ui 2025-11-08 18:47:09 +05:30
Byson94
b821d8bb6a feat: add widget-control command 2025-11-08 17:09:51 +05:30
Byson94
e12d5f7fb9 feat: add gtk_ui function for loading .ui 2025-11-07 20:22:56 +05:30
Byson94
26ee4e5560 chore: cargo fmt 2025-11-01 19:38:27 +05:30
Byson94
622a9c1b06 feat: bump to ewwii 0.3.1 2025-11-01 19:37:36 +05:30
Byson94
6b0e94698b fix: localsignal not working for non-gchararray props 2025-11-01 18:43:41 +05:30
Byson94
08e1f2d5f3 fix: circularprogress not updating to dyn vars 2025-11-01 18:30:43 +05:30
Byson94
b53431d861 chore: run cargo fmt 2025-11-01 12:22:30 +05:30
Byson94
327b223998 chore: add 0.3.0 release in changelog 2025-11-01 12:21:27 +05:30
Byson94
e26cd16ef9 fix: ensure full teardown of signals in reload 2025-11-01 10:29:04 +05:30
Byson94
669e7e9566 fix: minor issues with localsignals 2025-10-31 21:51:57 +05:30
Byson94
9bb8a9e3dd feat: turn localbind from a widget to a utility 2025-10-30 21:40:14 +05:30
Byson94
249cacdd0a chore: run cargo fmt 2025-10-30 19:47:28 +05:30
Byson94
167fe26f30 chore: notedown changes in changelog 2025-10-30 19:47:15 +05:30
Byson94
44a86e0937 feat: make localsignal simpler through localbind container 2025-10-30 19:45:43 +05:30
Byson94
f5e1b61dcf Revert "wip: adding localsignal support for all widget"
This reverts commit 7dc64014fd.
2025-10-30 15:11:08 +05:30
Byson94
1ca23373e1 Revert "wip: add localsignal support to eventbox"
This reverts commit 7095cb6637.
2025-10-30 15:11:06 +05:30
Byson94
78dafcc5b3 Revert "wip: add localsignal handling for basic props"
This reverts commit 74718b064f.
2025-10-30 15:11:05 +05:30
Byson94
e92ccab3d1 Revert "wip: adding more support to localsignal"
This reverts commit 19b790d4fa.
2025-10-30 15:11:03 +05:30
Byson94
8820a0f274 Revert "wip: making localsignal only bind once"
This reverts commit 8f15274fb9.
2025-10-30 15:11:02 +05:30
Byson94
9c15f8aed1 Revert "feat: make localsignal id relative to its props"
This reverts commit 9458d95222.
2025-10-30 15:11:00 +05:30
Byson94
f5ad63ed33 Revert "feat: moving initial of localsignal to a different place"
This reverts commit b3043afe04.
2025-10-30 15:10:59 +05:30
Byson94
7abc39d051 Revert "feat: add clear fn for localsignal store"
This reverts commit 60af15fb8d.
2025-10-30 15:10:58 +05:30
Byson94
47745fa501 Revert "feat: mvoe from widget binding to notify_local"
This reverts commit 2ac527bcbc.
2025-10-30 15:10:56 +05:30
Byson94
4c2e460127 Revert "feat: add localsignal support to many more widgets"
This reverts commit 902ca67c58.
2025-10-30 15:10:55 +05:30
Byson94
fa567b06d0 Revert "feat: add localsignal support to even moer widgets"
This reverts commit bfe1d248a4.
2025-10-30 15:10:53 +05:30
Byson94
892c09512c Revert "feat: add default text/markup values for gtk_label"
This reverts commit b369f648db.
2025-10-30 15:10:47 +05:30
Byson94
8be2f01058 Revert "fix: gtk_label default text/markup value being &str"
This reverts commit 91e7a86e7a.
2025-10-30 15:10:44 +05:30
Byson94
673ef2e09d Revert "feat: set gtk box space evenly to true"
This reverts commit 425ef5020b.
2025-10-30 15:10:36 +05:30
Byson94
cd271ed283 Revert "feat: moving all localsignal into a localbind wrapper"
This reverts commit 98e370ac60.
2025-10-30 15:10:19 +05:30
Byson94
98e370ac60 feat: moving all localsignal into a localbind wrapper 2025-10-30 15:00:00 +05:30
Byson94
425ef5020b feat: set gtk box space evenly to true 2025-10-30 14:12:10 +05:30
Byson94
91e7a86e7a fix: gtk_label default text/markup value being &str 2025-10-30 14:09:14 +05:30
Byson94
b369f648db feat: add default text/markup values for gtk_label 2025-10-30 14:07:18 +05:30
Byson94
bfe1d248a4 feat: add localsignal support to even moer widgets 2025-10-30 13:39:55 +05:30
Byson94
902ca67c58 feat: add localsignal support to many more widgets 2025-10-29 20:07:26 +05:30
Byson94
2ac527bcbc feat: mvoe from widget binding to notify_local 2025-10-29 16:16:58 +05:30
Byson94
60af15fb8d feat: add clear fn for localsignal store 2025-10-29 14:51:57 +05:30
Byson94
b3043afe04 feat: moving initial of localsignal to a different place 2025-10-29 14:23:26 +05:30
Byson94
9458d95222 feat: make localsignal id relative to its props 2025-10-29 13:56:25 +05:30
Byson94
8f15274fb9 wip: making localsignal only bind once 2025-10-29 13:33:41 +05:30
Byson94
19b790d4fa wip: adding more support to localsignal 2025-10-28 22:17:12 +05:30
Byson94
74718b064f wip: add localsignal handling for basic props 2025-10-28 21:51:46 +05:30
Byson94
7095cb6637 wip: add localsignal support to eventbox 2025-10-28 20:52:16 +05:30
Byson94
7dc64014fd wip: adding localsignal support for all widget 2025-10-28 20:14:32 +05:30
Byson94
66bc9f6d74 wip: localsignal for fast widget update 2025-10-27 22:25:49 +05:30
Byson94
4b3240b9b9 fix: example in ewii_plugin_api 2025-10-27 18:52:15 +05:30
Byson94
3803141f31 chore: run cargo fmt (again) 2025-10-27 18:13:21 +05:30
Byson94
2ed91786a3 chore: fix merge conflicts 2025-10-27 18:10:51 +05:30
Byson94
5e827a73c8 chore: run cargo fmt 2025-10-27 18:09:26 +05:30
Byson94
58957ec597 fix: poll/listen variables not working in other files 2025-10-27 18:08:56 +05:30
Byson94
abafe1b6fd fix: poll/listen variables not working in other files 2025-10-27 18:05:55 +05:30
Byson94
6536035e19 feat: add list_fns func to slib 2025-10-26 20:49:52 +05:30
Byson94
187a367977 feat: add support for circular-progress widget 2025-10-26 14:05:54 +05:30
Byson94
e0ae5c97d4 feat: add lifetime flag to update command 2025-10-25 15:47:08 +05:30
Byson94
59b3cdd0dc feat: implement recv drain in plugin load func 2025-10-24 21:00:03 +05:30
Byson94
5cf8659f92 feat: add default_select property to flowbox 2025-10-24 19:57:40 +05:30
Byson94
0b14629dd7 feat: move to rhai_trace 0.3.1 to not panic on certain err 2025-10-24 19:02:28 +05:30
Byson94
726093f534 fix: requiring child-index property on children of flowbox 2025-10-24 18:56:23 +05:30
Byson94
39a389b90c feat: add widget_name property to all widgets 2025-10-23 20:14:12 +05:30
Byson94
140643d4ec feat: add focusable property to all widget 2025-10-23 19:43:14 +05:30
Byson94
f52164da87 chore: run cargo fmt 2025-10-23 18:44:22 +05:30
Byson94
926c5bae7e feat: add flowbox widget 2025-10-22 22:11:51 +05:30
Byson94
ac8375bf47 fix: dont print error if message is '' 2025-10-22 18:03:21 +05:30
Byson94
58667c77ed fix: signals not exposed to other mod on first launch 2025-10-21 19:45:27 +05:30
Byson94
b06c3e0e3f feat: add new properties to eventbox 2025-10-21 18:52:30 +05:30
Byson94
21d50ed7e6 chore: fix one small comment 2025-10-20 22:23:24 +05:30
Byson94
5f576315fe chore: document change in changelog 2025-10-20 12:11:40 +05:30
Byson94
da57afb395 feat: change fnonce of register_function to fn 2025-10-20 12:06:01 +05:30
Byson94
c0ef865920 feat: add slib module for handling shared libs 2025-10-20 11:43:14 +05:30
Byson94
3afc8fa9ca fix: few eventbox drop target issue 2025-10-18 14:43:42 +05:30
Byson94
7a4191df95 chore(doc): improve ewwii_plugin_api doc comments 2025-10-14 21:32:24 +05:30
Byson94
18832c2956 feat(opt): add --with-plugin flag to daemon command 2025-10-14 19:58:46 +05:30
Byson94
4974df29d6 chore: feature 'Binary Dots' in readme 2025-10-14 17:58:20 +05:30
Byson94
b72700fbd2 Merge pull request #9 from BinaryHarbinger/main
Setting style provider priority to 900 still uses global theme as fallback just like GTK3 version.
2025-10-14 17:43:40 +05:30
BinaryHarbinger
361fe49001 fix: Style provider priority 'APPLICATION >> 900' 2025-10-13 20:57:24 +03:00
Byson94
db5e2055e1 chore: run cargofmt 2025-10-13 20:25:59 +05:30
Byson94
3ef6861b62 fix: remove VALBEF println clutter 2025-10-13 20:25:27 +05:30
Byson94
3156b2b85a Merge pull request #8 from Cyclic007/patch-1
add activateLinux example
2025-10-13 19:30:53 +05:30
Byson94
d2f8d3dfe3 Merge branch 'main' into iidev 2025-10-13 19:26:42 +05:30
Byson94
fc6f154021 Merge pull request #6 from BinaryHarbinger/main
Add additional support for dash
2025-10-13 19:24:39 +05:30
Byson94
f3b2f4bce7 fix(shell): move code to avoid repetetion 2025-10-13 19:14:58 +05:30
Byson94
edad7fb3b7 fix: inconsistencies between rhai_impl and ewwii_plugin_api 2025-10-13 19:02:22 +05:30
Cyclic
3631b67496 Create ewwii.scss 2025-10-12 14:47:54 +00:00
Cyclic
b118409e83 setup new example rhai 2025-10-12 14:47:12 +00:00
Byson94
34706b1ae8 fix: fix dep inconsistency through re-export 2025-10-12 18:50:40 +05:30
BinaryHarbinger
8927256f39 Add additional support for dash
Automatically detect dash and use it instead of sh if available
2025-10-12 16:11:05 +03:00
Byson94
6d667538db chore(remove): LICENSE-MIT which came after rebase 2025-10-12 15:18:38 +05:30
Byson94
54145617b5 fix(ci): incomplete 'check no-backend' step 2025-10-12 14:44:09 +05:30
Byson94
49057eec5d fix: all doctest err in ewwii_plugin_api 2025-10-12 14:36:23 +05:30
Byson94
b144571c9a fix: .gitignore conflict on merge 2025-10-12 14:26:49 +05:30
Byson94
30a4b63f00 fix: solve more cargo test issus 2025-10-12 14:11:59 +05:30
Byson94
0e2307ea03 fix(ci): Satisfying doctest 2025-10-12 14:11:59 +05:30
Byson94
dcdcc2dbc5 fix: fixing doctest issues with auto_plugin macro 2025-10-12 14:11:59 +05:30
Byson94
9a37ab6e7b fix(ci): using arch linux docker container 2025-10-12 14:11:59 +05:30
Byson94
b398ea4749 feat: adding panic handling 2025-10-12 14:11:59 +05:30
Byson94
c29cc74de0 feat: added onkeypress and onkeyrelease props 2025-10-12 14:11:59 +05:30
Byson94
f571c406c7 fix: fixed old widget creeping in issue 2025-10-12 14:11:59 +05:30
Byson94
517b31d62a fix(ci): fixing packages 2025-10-12 14:11:59 +05:30
Byson94
a6953310b1 chore: cargo fmt 2025-10-12 14:11:59 +05:30
Byson94
46591b1b8a feat: making examples in readme look good 2025-10-12 14:11:58 +05:30
Byson94
67c789ae41 fix(ci): Fixing indentation 2025-10-12 14:11:58 +05:30
Byson94
5ce4cedd3e fix(ci): Working on fixing the ci not running issue 2025-10-12 14:11:58 +05:30
Byson94
df5c4a0678 chore: added info in changelog 2025-10-12 14:11:58 +05:30
Byson94
29176aacf0 chore: fixing ewwii_plugin_api versioning issue 2025-10-12 14:11:58 +05:30
Byson94
345a57c465 feat: added max docs and 1 feat in ewwii_plugin_api 2025-10-12 14:11:58 +05:30
Byson94
bf1e1622ac fix: fixing feature issue in widget_backend.rs 2025-10-12 14:11:58 +05:30
Byson94
b9ce839f83 style: fixing ewwii_plugin_api Cargo.toml style 2025-10-12 14:11:58 +05:30
Byson94
8d025d1958 feat: improving the ewwii_plugin_api crate and fixing issues 2025-10-12 14:11:58 +05:30
Byson94
732acbdf5c feat: more api changes in ewwii_plugin_api 2025-10-12 14:11:58 +05:30
Byson94
975ebc7ee5 feat: improving docs and comments on ewwii_plugin_api 2025-10-12 14:11:58 +05:30
Byson94
03baaf3099 chore: fixing ewwii_plguin_api version issue 2025-10-12 14:11:58 +05:30
Byson94
7ba9d95f96 feat: added the ability to modify widgets 2025-10-12 14:11:58 +05:30
Byson94
3d08b3e873 chore: added change in changelog 2025-10-12 14:11:58 +05:30
Byson94
fa31aa9299 fix(rhai): fixing poll/listen scope not available on all modules 2025-10-12 14:11:58 +05:30
Byson94
7f72d0805e chore: Updating Cargo.lock 2025-10-12 14:11:58 +05:30
Byson94
ac5432c93c fix: fixing ewwii_plugin_api version 2025-10-12 14:11:58 +05:30
Byson94
19db669204 feat: expanding ewwii_plugin_api 2025-10-12 14:11:58 +05:30
Byson94
e13bb1f551 feat: added tools for exporting plugins 2025-10-12 14:11:58 +05:30
Byson94
8fbad0c9be feat: added option to list all widget ids in plugin sys 2025-10-12 14:11:58 +05:30
Byson94
994ee6de29 feat: added documentation for ewwii_plugin_api 2025-10-12 14:11:58 +05:30
Byson94
0bd437603b fix(ci): fixing dependencies on gtk4 2025-10-12 14:11:58 +05:30
Byson94
1891165d50 feat: updating changelog 2025-10-12 14:11:58 +05:30
Byson94
45657e145c feat: added readme and docs to plugin_api 2025-10-12 14:11:58 +05:30
Byson94
ca63ebe504 feat: fixing few errors and improving other stuff 2025-10-12 14:11:58 +05:30
Byson94
66951f8fba chore: cargo fmt 2025-10-12 14:11:58 +05:30
Byson94
609e023f9b fix: fixing issue templates 2025-10-12 14:11:58 +05:30
Byson94
294ae868a6 feat: Updating changelog 2025-10-12 14:11:58 +05:30
Byson94
0e15ffa23a feat: getting rhai mod working via plugin 2025-10-12 14:11:58 +05:30
Byson94
5f62a00c60 feat: got library system loading working 2025-10-12 14:11:58 +05:30
Byson94
bf1b24bb95 wip: bin level plugin system 2025-10-12 14:11:58 +05:30
Byson94
27dacde798 fix: fixed focusable not working 2025-10-12 14:11:58 +05:30
Byson94
9004f186fa feat: adding the release date in changelog 2025-10-12 14:11:58 +05:30
Byson94
b9e6e8ba0c fix: fixing minor issues 2025-10-12 14:11:58 +05:30
Byson94
ed15ddfaaa feat: removed centerbox widget 2025-10-12 14:11:58 +05:30
Byson94
881910f1b3 GREATEST FEAT: Achived sticky windows in x11 2025-10-12 14:11:58 +05:30
Byson94
7936e4f881 fix: fixed gif images not working 2025-10-12 14:11:58 +05:30
Byson94
9072ff8525 fix: did a simple fix over the scale drag issue 2025-10-12 14:11:58 +05:30
Byson94
1c85310c22 chore: Cargo.lock update 2025-10-12 14:11:58 +05:30
Byson94
b65c1b5c23 feat: added deprecated section in changelog 2025-10-12 14:11:58 +05:30
Byson94
2840138464 feat: added information about alpha release in changelog 2025-10-12 14:11:58 +05:30
Byson94
205fe2f9bf fix: fixing all compiler warnings 2025-10-12 14:11:58 +05:30
Byson94
6a7e77029d feat: fixed image wdgt and added icon wdgt 2025-10-12 14:11:58 +05:30
Byson94
f36cb58237 fix: no classes to window to prevent x11 issues 2025-10-12 14:11:58 +05:30
Byson94
c14f2b4fc6 feat: fixing warnings & messin with x11 2025-10-12 14:11:58 +05:30
Byson94
243408d176 fix(x11): fixing widget not realized before getting surface 2025-10-12 14:11:58 +05:30
Byson94
b647eb36af fix: fixing the ewwii window close order 2025-10-12 14:11:58 +05:30
Byson94
4238b5fe9a feat: fixing application loop issues by doing manual one 2025-10-12 14:11:58 +05:30
Byson94
3120d7556e feat: fixing very single gtk4 migration error! yippee! 2025-10-12 14:11:58 +05:30
Byson94
a9ccb25cae fix: fixing one of the last few err (i think so) 2025-10-12 14:11:58 +05:30
Byson94
6d32f4f823 fix: even more fixing of gtk4 errors 2025-10-12 14:11:58 +05:30
Byson94
22f9e7e272 fix: fixing the drag thingy in eventbox 2025-10-12 14:11:58 +05:30
Byson94
e73173888c fix: fixing broken wifi gui manager screenshot 2025-10-12 14:11:58 +05:30
Byson94
faf8afdd65 feat: added name in 0.2.0 release changelog 2025-10-12 14:11:58 +05:30
Byson94
f2fc0ca891 fix: fixing more errors that i cant name of 2025-10-12 14:11:58 +05:30
Byson94
e7346cb28a fix: fixing many many more gtk4 migration errs 2025-10-12 14:11:58 +05:30
Byson94
bbc99df84d fix: added controlflow in gif render part 2025-10-12 14:11:58 +05:30
Byson94
cf0367e069 fix: fixing more gtk4 related err 2025-10-12 14:11:58 +05:30
Byson94
1f1b1b2b01 feat: added EventControllerKey in widget_definition.rs 2025-10-12 14:11:58 +05:30
Byson94
a6841935cc fix: fixing gtk_layer_shell naming issues 2025-10-12 14:11:58 +05:30
Byson94
96039a47aa fix: fixing glib weak reference issues 2025-10-12 14:11:58 +05:30
Byson94
2d30974f85 fix: fixing timeouts in eventbox being a borrow 2025-10-12 14:11:57 +05:30
Byson94
c5949be319 feat(rewrite): button and event_box widgets 2025-10-12 14:11:44 +05:30
Byson94
f21833b641 feat: rewriting eventbox to work with gtk4 2025-10-12 14:11:44 +05:30
Byson94
35cb12b2f2 fix: fixing even more errors in widget_definition 2025-10-12 14:11:44 +05:30
Byson94
4efd34f78b fix: fixing glib::clone! in widget_definitions.rust 2025-10-12 14:11:44 +05:30
Byson94
d35161d5d1 fix: fixed connect_monitor_added function 2025-10-12 14:11:44 +05:30
Byson94
fd73225688 fix: fixed style_provider reelated err 2025-10-12 14:11:44 +05:30
Byson94
b7b7f15420 fix: more errors and follow new gtk4 model 2025-10-12 14:11:22 +05:30
Byson94
b910eb741a feat: stubbing widget creation logic for easier migration 2025-10-12 14:11:22 +05:30
Byson94
fec83417f5 wip: migrate to gtk4 2025-10-12 14:11:10 +05:30
Byson94
b3f2c3f8a9 chore: a small rename :) 2025-10-12 14:11:10 +05:30
Byson94
1eaf773206 feat: removed monitor lib in favour of gtk4 2025-10-12 14:11:10 +05:30
Byson94
38222328c2 feat: fixed falure send & added print flag to engine-override 2025-10-12 14:11:10 +05:30
Byson94
dc04096587 feat: better management of rhai crate 2025-10-12 14:11:10 +05:30
Byson94
52182d3b4e feat: removed more code/dependencies 2025-10-12 14:11:10 +05:30
Byson94
c59f3f3683 feat: removed unnecessary dependencies 2025-10-12 14:11:10 +05:30
Byson94
348b4bcf98 chore: cargo fmt 2025-10-12 14:11:10 +05:30
Byson94
f811704e5e feat: added file path indicator in errors! 2025-10-12 14:11:10 +05:30
Byson94
c2867b6fc8 fix: resolver throwing err at import on p/e err 2025-10-12 14:11:10 +05:30
Byson94
972a5bb357 feat: even better err support with rhai_trace v3 2025-10-12 14:11:10 +05:30
Byson94
eb73c5b08f feat: renaming setup_for_rt to setup_dyn_ids 2025-10-12 14:11:10 +05:30
Byson94
53607bf019 feat: removed INPUT_VAL var support in input wdgt 2025-10-12 14:11:10 +05:30
Byson94
21216b642c feat: improved the module heading generation 2025-10-12 14:11:10 +05:30
Byson94
39c284682a fix: fixed doc error 2025-10-12 14:11:10 +05:30
Byson94
5d367195ed chore: removed lagacy support on focusable win prop 2025-10-12 14:11:10 +05:30
Byson94
b38ed83289 feat: added force_normal property for windows 2025-10-12 14:11:10 +05:30
Byson94
22ae76c2ce fix: m/min duration end & ewwii crash 2025-10-12 14:11:10 +05:30
Byson94
1ff85fef2f fix: fixed broken m value for min 2025-10-12 14:11:10 +05:30
Byson94
160ec69391 feat: improving the engine-override feature 2025-10-12 14:11:10 +05:30
Byson94
a97e5fb762 fix(typo): in 0.2.0 CHANGELOG 2025-10-12 14:11:10 +05:30
Byson94
98214786d8 feat: added engine-override command 2025-10-12 14:11:10 +05:30
Byson94
01e6f2d6b8 feat(stdlib): added std::regex for regex matching 2025-10-12 14:11:10 +05:30
Byson94
9a92276bd0 chore: removed added support for open-many in v0.2.0 2025-10-12 14:11:10 +05:30
Byson94
d491a76c43 feat: improved rhai docs generator 2025-10-12 14:11:10 +05:30
Byson94
9592f9333a fix(generate-rhai-docs): broken code for docusaurus 2025-10-12 14:10:50 +05:30
Byson94
fda4b68c56 feat: remove stuff in autogen 2025-10-12 14:10:20 +05:30
Byson94
317b6162ea feat(generate-rhai-docs): fix mdx issues 2025-10-12 14:10:07 +05:30
Byson94
5dc6f3be30 feat: renaming js => javascript 2025-10-12 14:08:30 +05:30
Byson94
a999cc8948 feat: remove docs/ and moving to docusarus 2025-10-12 14:08:21 +05:30
Byson94
ef700d9d85 feat: added m as another duration unit for minute 2025-10-12 14:08:21 +05:30
Byson94
bd6e540d04 feat: more renaming | eww => ewwii 2025-10-12 14:08:21 +05:30
Byson94
68e0f9e35a feat: removing a few comment of minee 2025-10-12 14:08:21 +05:30
Byson94
9e3fcf2e09 feat: fully renamed EwwPaths => EwwiiPaths 2025-10-12 14:08:20 +05:30
Byson94
34019e65ba feat: made eval_code fn use the compile_code fn to compile 2025-10-12 14:08:20 +05:30
Byson94
df3fb81b6a chore: renaming a few eww => ewwii 2025-10-12 14:08:20 +05:30
Byson94
4fabb49ed3 feat: added state command and fixed log issues 2025-10-12 14:08:20 +05:30
Byson94
7db5a1305c feat: added --preserve in the docs 2025-10-12 14:08:20 +05:30
Byson94
14b5cff081 feat: added date in 0.1.4 release 2025-10-12 14:08:20 +05:30
Byson94
89bb47e53e feat: added perserve flag to the update command which preserves the new updates 2025-10-12 14:08:20 +05:30
Byson94
14b54e7790 chore(cargo): building for 0.1.3 2025-10-12 14:08:20 +05:30
Byson94
b0044d6f0a feat: fixed image_width and height not working issue for image 2025-10-12 14:08:20 +05:30
Byson94
c0f1a2e1c8 feat: removed getting started section from docs 2025-10-12 14:08:20 +05:30
Byson94
50d95f1870 chore: fixing a comment... 2025-10-12 14:08:20 +05:30
Byson94
e4187a6d4c feat: reworking the inject-vars arg of update 2025-10-12 14:08:20 +05:30
Byson94
b5a639936b feat: made update cmd so that it will preserve current wdgt state 2025-10-12 14:08:20 +05:30
Byson94
4ca6716074 feat(docs): added exclusive true in config 2025-10-12 14:08:20 +05:30
Byson94
1937508781 fix(docs): fixed invalid config in configuration.md 2025-10-12 14:08:20 +05:30
Byson94
37be5104b2 feat: updating ewwii dark theme 2025-10-12 14:08:20 +05:30
Byson94
976f0b4e34 feat: improved parent-death sig on freebsd and linux 2025-10-12 14:08:20 +05:30
Byson94
ab1b2a8a96 feat: removed todo 2025-10-12 14:08:20 +05:30
Byson94
2081d13b81 chore: cargo formatting 2025-10-12 14:08:20 +05:30
Byson94
164b7858f7 feat: added date to 0.1.1 release in changelog 2025-10-12 14:08:20 +05:30
Byson94
fb528adeb9 feat: migrated to rhai_trace 0.2.0 2025-10-12 14:08:20 +05:30
Byson94
4aecfa43ab perf: improved poll handling and made it reusable 2025-10-12 14:08:20 +05:30
Byson94
6bc8822e04 feat: made update not require a window argument 2025-10-12 14:08:20 +05:30
Byson94
095e99bc0a feat: fix poll/listen not working for multiple window 2025-10-12 14:07:28 +05:30
Byson94
623a79da54 feat: removed unused deps 2025-10-12 14:07:16 +05:30
Byson94
9e9d57be8e feat: fixing documentation mistake which said main.rhai as main file 2025-10-12 14:07:16 +05:30
Byson94
cbe77b3b1f feat: adding release date of 0.1.0 2025-10-12 14:07:16 +05:30
Byson94
aff9a999b0 feat(theme): improved docs ewwii theme 2025-10-12 14:07:16 +05:30
Byson94
588f843e0c feat: updated wifi gui template image 2025-10-12 14:07:16 +05:30
Byson94
0957ef2804 chore: Update README.md 2025-10-12 14:07:06 +05:30
Byson94
c571bfb03b feat: removed exit on config error mechanism of daemon 2025-10-12 14:07:06 +05:30
Byson94
d7445dabb2 fix: fixed dyn var extractor not skipping comments issue 2025-10-12 14:07:06 +05:30
Byson94
1dac50f70a fix: fixed broken call-fns command 2025-10-12 14:07:06 +05:30
Byson94
3a04893caa fix: fixed default poll/listen variables not working in external modules 2025-10-12 14:07:06 +05:30
Byson94
83c292de6b feat: improved both resolver failed error 2025-10-12 14:07:06 +05:30
Byson94
a2e18ee7df feat: fixed mod resolver not printing error issue 2025-10-12 14:07:06 +05:30
Byson94
84c455f557 feat: many internal changes regarding enter(..)
1. Made it so that there wont be any error if user didnt return anything
2. Added support for defining multiple enter(..)
3. Added support for merging multiple enter(..)
2025-10-12 14:07:06 +05:30
Byson94
22981292cc feat: better errors with rhai_trace (lib built for ewwii)
With this commit, Ewwii now integrates rhai_trace, which is a rust library that I
built to generate better errors with rhai.

It is plugged into codespan reporting for providing awesome errors which
makes programming with ewwii much easier! It is a major UX update along
with the removal of `dyn_id` dependance.
2025-10-12 14:07:06 +05:30
Byson94
4d0d84c44d docs: improved the doc flow 2025-10-12 14:07:06 +05:30
Byson94
b9d5fd8e68 feat: added TODO.md 2025-10-12 14:07:06 +05:30
Byson94
826d560b84 feat: improved the removed dyn_id part in changelog 2025-10-12 14:07:06 +05:30
Byson94
5b47b67c83 feat(MAJOR): removed the need for dyn_id
This is a very very very big update for UX! Ewwii finally has support
for automatically assigning `dyn_id`.

This was actually not as hard as I thought! I just had to mutate the
widget AST and inject a `dyn_id` in based on its parent.

It works soooo well and the burden on the user just reduced sooo much!
2025-10-12 14:07:06 +05:30
Byson94
0561ecba3f feat(docs): added wifi_manager template 2025-10-12 14:07:06 +05:30
Byson94
c9631268bd feat(rename): renamed iirhai --> rhai_impl 2025-10-12 14:07:06 +05:30
Byson94
11510bd8b4 feat(github): added module request issue template 2025-10-12 14:06:39 +05:30
Byson94
a58694a04d feat: renamed widgetnode.rs to ast.rs in iirhai 2025-10-12 14:06:39 +05:30
Byson94
9667a73b05 feat: removed modules example 2025-10-12 14:06:39 +05:30
Byson94
24b57fb269 feat: updated readme contribewwtiing steps 2025-10-12 14:06:39 +05:30
Byson94
cf1dc459a2 feat: removed statictranspl docs 2025-10-12 14:06:39 +05:30
Byson94
306c4c542b style: remove pre_description having \t at start 2025-10-12 14:06:05 +05:30
Byson94
61bb98460d feat: added contributing.md 2025-10-12 14:03:22 +05:30
Byson94
53116df6a0 feat: added api::linux library for rhai 2025-10-12 14:03:16 +05:30
Byson94
29c70d26c7 feat: remove unused stuff and do namespace changes 2025-10-12 14:03:01 +05:30
Byson94
ee40a2265a fix: borken % based w/h in window def 2025-10-12 14:02:41 +05:30
Byson94
167df98182 style: fixed pre_description having \t at the start 2025-10-12 14:02:41 +05:30
Byson94
904ceb8f09 feat(ci): added gh-page publishment on changes in generate-rhai-docs/** 2025-10-12 14:02:41 +05:30
Byson94
307ba2fe61 feat: added feature for adding descriptions in generated mod docs 2025-10-12 14:02:41 +05:30
Byson94
87b615999d chore: ran cargo fmt 2025-10-12 14:02:41 +05:30
Byson94
1e8ab7f983 fix(ci): added necessary dependencies that need to be installed 2025-10-12 14:02:41 +05:30
Byson94
2293993865 fix(ci): fixed broken rust install 2025-10-12 14:02:41 +05:30
Byson94
69bf08729d fix(ci): outdated cache using v2 2025-10-12 14:01:47 +05:30
Byson94
4fa04a4b6e feat(ci): added module docs generation logic in workflow 2025-10-12 14:01:47 +05:30
Byson94
0572480239 feat: finished writing apilib docs 2025-10-12 14:01:47 +05:30
Byson94
092ecb407d wip: Massive doc automation and improvement. 2025-10-12 14:01:47 +05:30
Byson94
e63886b402 feat: improved poll/listen handler setup/shutdown in ewwii open/close cmd 2025-10-12 14:01:47 +05:30
Byson94
4b18b6c4f1 feat: DID MASSIVE improvements and changes that i cant summarize
So basically, i added sigint/sigterm catching to kill all children cleanly, then i fixed the std::monitor module as it returned tuple which didnt work with rhai much, and then i removed the deprecated attribute warning thingy. Yeah thats it.
2025-10-12 14:01:47 +05:30
Byson94
678c17c80f docs: fixed update docs 2025-10-12 14:01:47 +05:30
Byson94
076cba0c85 docs: moved advanced commands below examples 2025-10-12 14:01:47 +05:30
Byson94
f9beb589db docs: improved variables config documentation 2025-10-12 14:01:47 +05:30
Byson94
3f439a2aa0 docs: improved docs by adding commands/ section 2025-10-12 14:01:47 +05:30
Byson94
b8927722aa fix: removed kill_state_change_handler() from close_window bcz it causes issues 2025-10-12 14:01:47 +05:30
Byson94
a0945ecc84 feat: added rhai to workspace 2025-10-12 14:01:47 +05:30
Byson94
e51bdc3647 perf: replaced Arc<> with Rc<> in single threaded instances 2025-10-12 14:01:47 +05:30
Byson94
a6b87183e1 feat: added good syntax highlighting for ewwii theme 2025-10-12 14:01:47 +05:30
Byson94
a491077fca fix: fixed zombie process creation due to 100ms read time 2025-10-12 14:01:47 +05:30
Byson94
2a369d0532 feat: introduced 16ms (1 frame) batching for ewwii 2025-10-12 14:01:47 +05:30
Byson94
e83239af24 feat: fixed [NoBackend] not existing 2025-10-12 14:01:47 +05:30
Byson94
34a4664499 feat: set release date of 0.1.0-beta as today 2025-10-12 14:01:47 +05:30
Byson94
1606c61745 fix: fixed slider updating value while dragging issue 2025-10-12 14:01:47 +05:30
Byson94
ade3c45be5 feat: made listen similar to eww's model to avoid kill issues 2025-10-12 14:01:47 +05:30
Byson94
7efda1ba42 feat: added kill_state_change_handler call in stop_application 2025-10-12 14:01:47 +05:30
Byson94
25c9890fe3 fix: fixed warnings on child 2025-10-12 14:01:47 +05:30
Byson94
baf8c71909 fix: preventing early terminiation of listen child 2025-10-12 14:01:47 +05:30
Byson94
369d75af26 perf: improved perf in ewwii_config.rs by decreaasing amount of clone 2025-10-12 14:01:47 +05:30
Byson94
cf66eb8853 perf: moved from deep cloning/owning to borrowing data 2025-10-12 14:01:47 +05:30
Byson94
f714c9211d fix: removed rhai full optimization as it caused issues 2025-10-12 14:01:47 +05:30
Byson94
2d34db53fe style: made widget of template1 400 2025-10-12 14:01:47 +05:30
Byson94
a7f0065e85 feat: added templates section in README 2025-10-12 14:01:47 +05:30
Byson94
8fdb800dea docs: added proper site url to book.toml 2025-10-12 14:01:47 +05:30
Byson94
a747c5baba Stop tracking docs/book 2025-10-12 14:01:47 +05:30
Byson94
b4238e06b3 feat(license): make eww/ewwii licensing more clear 2025-10-12 14:01:33 +05:30
Byson94
2d2e288f5c perf: added full rhai optmization for speed 2025-10-12 13:59:49 +05:30
Byson94
1326716b0b feat: upgraded to rust 1.89.0, fixed new compiler warnings 2025-10-12 13:59:49 +05:30
Byson94
a6254b4308 docs(style): added ewwii (dark) theme that matches starlight theme 2025-10-12 13:59:49 +05:30
Byson94
f1e2e9561c docs(style): set navy as default dark theme 2025-10-12 13:59:49 +05:30
Byson94
1d5f3bbecd feat: replaced rust,ignore with js 2025-10-12 13:59:49 +05:30
Byson94
a435cb8a20 feat: replaced default theme with catuppuccin 2025-10-12 13:59:49 +05:30
Byson94
d3d0c59a80 docs: added approximation symbol to equations using pi (float num) 2025-10-12 13:59:49 +05:30
Byson94
f947c97b83 feat: improved wifi apilib and fixed prop conversion errors 2025-10-12 13:59:49 +05:30
Byson94
5d53c08fd7 feat: added parse error handling for simplefileresolver 2025-10-12 13:59:49 +05:30
Byson94
cb52e7047c feat: added INPUT_VAL env comamnd to props of input widget 2025-10-12 13:59:49 +05:30
Byson94
23a7d22062 feat: added std::command module to rhai 2025-10-12 13:59:49 +05:30
Byson94
af7cf437a3 fix: fixed stored_widget_node not existing on first run issue 2025-10-12 13:59:49 +05:30
Byson94
adfa2d9c58 feat: added update and call-fns command to ewwii 2025-10-12 13:59:49 +05:30
Byson94
62d6092956 feat: added pretty logging for rhai ParseError 2025-10-12 13:59:49 +05:30
Byson94
84bd33bfd6 docs: statictranspl --> staticscript 2025-10-12 13:59:49 +05:30
Byson94
9b13f4cea7 docs: added section for user defined modules in ewwii docs module section 2025-10-12 13:59:49 +05:30
Byson94
e2614a9832 feat: made ewwii print errors from external modules 2025-10-12 13:59:49 +05:30
Byson94
d1ecb16823 feat: added dyn_id property to hash props test fn 2025-10-12 13:59:49 +05:30
Byson94
e21d8fd836 pref: made WidgetInfo borrow data instead of owning it to improve perf 2025-10-12 13:59:49 +05:30
Byson94
2ffac9e6ca pref: removed .clone() of props from WindowInitiator::new() 2025-10-12 13:59:49 +05:30
Byson94
9de5181aaa perf: made root_node Arc<WidgetNode> to safe resources | ewwii_config.rs 2025-10-12 13:59:49 +05:30
Byson94
e35477aecc feat: removed rc wrapping form store as store was already wrapped in arc 2025-10-12 13:59:49 +05:30
Byson94
9930241970 perf: wrapped compiled_ast in arc to avoid data clone 2025-10-12 13:59:49 +05:30
Byson94
494a8da3f7 feat: removed instant::now() tracking on re-eval sys 2025-10-12 13:59:49 +05:30
Byson94
aa25c4735e perf: made store a rc to avoid clones | app.rs:371 2025-10-12 13:59:49 +05:30
Byson94
1d260f65bc perf: cached parser in re-eval system for speed 2025-10-12 13:59:49 +05:30
Byson94
3c6cbe1638 feat: improved runtime error handling of widgetnode casting 2025-10-12 13:59:49 +05:30
Byson94
fa63a99ae3 feat: reduced max_width to 100 and ran cargo fmt 2025-10-12 13:59:49 +05:30
Byson94
da1c22f525 perf: reusing compiled config to save performance and time 2025-10-12 13:59:49 +05:30
Byson94
70bbb630cc feat: stopped setting homogeneous in space_even prop is not provided 2025-10-12 13:59:49 +05:30
Byson94
44814071c8 feat: removed seen_widget_ids logic in diffing system 2025-10-12 13:59:49 +05:30
Byson94
d3a8f22bfb feat: added propagate_natural_height property to scrolledwindow 2025-10-12 13:59:49 +05:30
Byson94
43b31cb814 feat: added trace log lvel support 2025-10-12 13:59:49 +05:30
Byson94
40d5972533 feat: trace can be enabled if EWWII_TRACE env var exists 2025-10-12 13:59:49 +05:30
Byson94
26329aa75f feat: updated docs to be up-to-date with the latest changes 2025-10-12 13:59:49 +05:30
Byson94
4b698f48fa feat: made resolve_rhai_widget_attrs not rely on widgetnode -- less work for maintainers 2025-10-12 13:59:49 +05:30
Byson94
786ebf04d4 chore: ran cargo fmt 2025-10-12 13:59:49 +05:30
Byson94
9fbfcca1c2 feat: added std::math lib for rhai 2025-10-12 13:59:49 +05:30
Byson94
daf11c8d1a chore: ran cargo fmt 2025-10-12 13:59:49 +05:30
Byson94
28809ff783 fix: fixed reordering issues in dyn system
Fixed the reodering issue in the dynamic update system which was caused
by the use of `pack_end()` for adding a child to box_container in the create_widget() fn.

`pack_end()` is a horrible option as it would require hardcoding
"expanded", "fill" etc.

This fix uses the better option `add()` which fixes all the reodering
issues.

According to all the tests, (did 15+ tests in a dyn fn that would return 3
widgets) non of them had ordering issues. So, I will mark this as a fix.
2025-10-12 13:59:48 +05:30
Byson94
48dbdeb5c6 feat: made dyn sys a lil better by obliterating redundant wdgts 2025-10-12 13:59:48 +05:30
Byson94
09bba44229 feat: added better error handling for fnnotdefined 2025-10-12 13:59:48 +05:30
Byson94
6e8207cb56 feat: made window resizable by default. 2025-10-12 13:59:48 +05:30
Byson94
557254f74a refactor: removed unnecessary println! in app 530 2025-10-12 13:59:48 +05:30
Byson94
3a72ddf3d7 fix: fixed stdlib shadowed by apilib issue 2025-10-12 13:59:48 +05:30
Byson94
043892d23b feat: made x,y,widget,height props optional 2025-10-12 13:59:48 +05:30
Byson94
0490604277 feat: added get_adapter_connectivity() fn to api::wifi 2025-10-12 13:59:48 +05:30
Byson94
584a6e5911 chore: ran cargo fmt 2025-10-12 13:59:48 +05:30
Byson94
049a0337b2 feat: fixed broken current_connection_linux command 2025-10-12 13:59:48 +05:30
Byson94
a42cc5df37 chore: added new feats to changelog.md 2025-10-12 13:59:48 +05:30
Byson94
4d29933271 feat: added api module to rhai 2025-10-12 13:59:48 +05:30
Byson94
00ba7af473 feat: added json module to rhai 2025-10-12 13:59:48 +05:30
Byson94
f2a177bc79 feat: added fix in changelog.md 2025-10-12 13:59:48 +05:30
Byson94
04aff76ea4 chore: ran cargo fmt 2025-10-12 13:59:48 +05:30
Byson94
6ff1c07c42 fix: fixed window reopening issue after closing it. 2025-10-12 13:59:48 +05:30
Byson94
c23d0d29dd feat: added kill switch for poll/listen 2025-10-12 13:59:48 +05:30
Byson94
e8d08f8c43 style: removed redundant , in readme 2025-10-12 13:59:48 +05:30
Byson94
9b6f310d4f feat: added data struct example in readme 2025-10-12 13:59:48 +05:30
Byson94
f281396b6f feat: removed .yuck files from ewwii examples 2025-10-12 13:59:48 +05:30
Byson94
95ebf251a2 chore: ran cargo fmt 2025-10-12 13:59:48 +05:30
Byson94
a14163bfca feat: removed root_widget param of update_widget_tree() 2025-10-12 13:58:36 +05:30
Byson94
c64d4e5cb5 fix: removed unused id computation in get_id_to_widget_info() 2025-10-12 13:58:36 +05:30
Byson94
9bd70df8bf feat: did some stuff that could possible make create widget work 2025-10-12 13:58:36 +05:30
Byson94
8f68cc6abc chore: ran cargo fmt 2025-10-12 13:58:36 +05:30
Byson94
59709c3ddf feat: made diffing sys work | possibly
The diffing system works with updates -- as seen from tests but the widget removal/creation system is still untested and may still need more iterations.
2025-10-12 13:58:36 +05:30
Byson94
405e50effb fix: fixed widget.clone() errors 2025-10-12 13:58:36 +05:30
Byson94
631809af3c feat: added a better diffing system 2025-10-12 13:58:36 +05:30
Byson94
90f95c2963 feat: got a minimal prototype of the diffing system 2025-10-12 13:58:36 +05:30
Byson94
c252d2d554 feat: fixed broken get_home_dir() fn in std::env | rhai 2025-10-12 13:58:36 +05:30
Byson94
fdb1697ead feat: added example section and section for statctranspl 2025-10-12 13:58:36 +05:30
Byson94
045784f1f9 chore: ran cargo fmt 2025-10-12 13:58:36 +05:30
Byson94
ef13f128ed feat: fixed all compilation warnings 2025-10-12 13:58:36 +05:30
Byson94
ef46ac9d0e feat: added dyn prop support to all widgets i thnk 2025-10-12 13:58:36 +05:30
Byson94
b430e4929b feat: added kill_on_drop for listener 2025-10-12 13:58:36 +05:30
Byson94
831fe185e3 feat: added prop dyn support to more widgets 2025-10-12 13:58:36 +05:30
Byson94
fdb9c08895 feat: replaced byson94 -> Ewwii-sh 2025-10-12 13:58:36 +05:30
Byson94
abe0a37aca fix: fixed doc name and replaced eww with ewwii 2025-10-12 13:58:36 +05:30
Byson94
9f86286e57 feat: added home-button to docs 2025-10-12 13:58:36 +05:30
Byson94
9ada74d70b feat: added docs back in 2025-10-12 13:58:36 +05:30
Byson94
855f59b407 feat: replaced apache-2.0 with gplv3 2025-10-12 13:58:35 +05:30
Byson94
edcdbcc577 feat: apache-2.0 to gpl3. Making ewwii fully open source. 2025-10-12 13:58:35 +05:30
Byson94
a24daad15e fix: removed redundant argument from fn which caused errors 2025-10-12 13:58:35 +05:30
Byson94
203f5418fa feat: moved docs to Ewwii-sh/docs 2025-10-12 13:58:35 +05:30
Byson94
30c37a1fa4 chore: run cargo fmt 2025-10-12 13:58:35 +05:30
Byson94
863bfb5cf4 feat: added modules example 2025-10-12 13:58:35 +05:30
Byson94
f8bc0835d0 feat: Added monitor stdlib 2025-10-12 13:58:35 +05:30
Byson94
bef32197da feat: added data-structs example 2025-10-12 13:58:35 +05:30
Byson94
1b08627c44 Replaced example screenshot 2025-10-12 13:58:35 +05:30
Byson94
501cf8869d chore: ran cargo fmt 2025-10-12 13:58:35 +05:30
Byson94
5d272ce420 feat: added example, fixed small bugs 2025-10-12 13:58:35 +05:30
Byson94
e15762c725 docs: added dyn_id docs 2025-10-12 13:58:35 +05:30
Byson94
a8f54306aa hell: I AM SORRY! I CAUSED PAIN UPON ALL USERS
I MADE KEYS A REQUIRED FACTOR FOR DYNAMIC UPDATES.... I AM REALLY SORRY, I HAD NO OTHER OPTIONS OTHAT THAN THIS MAJOR UX FLAW...
2025-10-12 13:58:35 +05:30
Byson94
664412553d feat: Added dynamic prop update support 2025-10-12 13:58:35 +05:30
Byson94
b512a35e3d feat: added recursive support for get_id_to_props_map 2025-10-12 13:58:35 +05:30
Byson94
6e3268ca17 feat: implmented half working dynamic update system 2025-10-12 13:58:35 +05:30
Byson94
03a2bcf37e feat: Build custom module resolver for rhai 2025-10-12 13:58:35 +05:30
Byson94
6f65d127aa docs: rearranged expr_lang.md path 2025-10-12 13:58:35 +05:30
Byson94
1034c44ee3 docs: readded expression_language, fixed str interpolation eg 2025-10-12 13:58:35 +05:30
Byson94
4ec6fe308c fix: replaced include() fn in config doc as it got removed 2025-10-12 13:58:35 +05:30
Byson94
01b05b0bda fix: fixed first window example in config::configuration.md 2025-10-12 13:58:35 +05:30
Byson94
d377fc409f feat: Added Widget Properties and widget param doc
Added widget properties (what they do & their signature), the parameters of the widget function call.

I may have also improved the docs on some other parts which I may have forgotten.
2025-10-12 13:58:35 +05:30
Byson94
c2c0c7e292 feat: removed include because rhai supports import/export 2025-10-12 13:58:35 +05:30
Byson94
8609a6acb7 feat: Added example 2025-10-12 13:58:35 +05:30
Byson94
7142567848 chore: ran cargo fmt 2025-10-12 13:58:35 +05:30
Byson94
58c93576b2 feat: Added support for stack widget 2025-10-12 13:58:35 +05:30
Byson94
5a4972f940 feat: Added transform widget support. 2025-10-12 13:58:35 +05:30
Byson94
64f94a11cd feat: Added tooltip, clrchooser, clrbttn widget support 2025-10-12 13:58:35 +05:30
Byson94
45745ed381 chore: ran cargo fmt 2025-10-12 13:58:35 +05:30
Byson94
6834b664fd feat: Added basic and fragile dyn sys for poll/listen 2025-10-12 13:58:35 +05:30
Byson94
f03fbbfafe refactor: Fixed many warnings 2025-10-12 13:58:35 +05:30
Byson94
ef52f691cf chore(rebrand): replaced eww to ewwii 2025-10-12 13:58:23 +05:30
Byson94
273fdfb082 feat: Added compile_code fn to parser 2025-10-12 13:58:23 +05:30
Byson94
1de8c6998a doc: Added modules documentation 2025-10-12 13:58:23 +05:30
Byson94
cc21ff1863 feat: Added std module with text and env submodule 2025-10-12 13:58:23 +05:30
Byson94
ea0f4d091a feat: added better dynamic update 2025-10-12 13:58:23 +05:30
Byson94
4a43eabed2 docs: fixed config & syntax file path 2025-10-12 13:58:23 +05:30
Byson94
9d18de1110 docs: improved docs and added nested chapters 2025-10-12 13:58:23 +05:30
Byson94
87984cdaa8 docs: improved docs by refactoring the configuration.md 2025-10-12 13:58:23 +05:30
Byson94
ab5d8ce28a feat: added gtk event box support 2025-10-12 13:58:23 +05:30
Byson94
118af53cc8 feat: added gtk scrolledwindow support 2025-10-12 13:58:23 +05:30
Byson94
d7564d716f feat: Added gtk calendar support 2025-10-12 13:58:23 +05:30
Byson94
69d8523b85 chore: ran cargo fmt 2025-10-12 13:58:23 +05:30
Byson94
535cf3b3ba feat: added gtk input support and changed optional logic 2025-10-12 13:58:23 +05:30
Byson94
ddaffd166a feat: added support for gtk image 2025-10-12 13:58:23 +05:30
Byson94
7bec8e42ce feat: added support for graph 2025-10-12 13:58:23 +05:30
Byson94
5e3f538674 feat: added support for circular progress bar 2025-10-12 13:58:23 +05:30
Byson94
530ccca1e4 feat: added support for gtk expander 2025-10-12 13:58:23 +05:30
Byson94
f1a7cfb48c feat: added support for gtk_combo_box_text 2025-10-12 13:58:23 +05:30
Byson94
6b9fec8ef7 chore: removed unnecessary test 2025-10-12 13:58:23 +05:30
Byson94
322311bda3 chore: fixed logo path 2025-10-12 13:58:23 +05:30
Byson94
643db859a3 chore: Changed logo 2025-10-12 13:58:23 +05:30
Byson94
e374a203ff chore: ran cargo fmt 2025-10-12 13:58:23 +05:30
Byson94
21658b19bc feat: Added support for gtk revealer widget 2025-10-12 13:58:23 +05:30
Byson94
d6b439f097 feat: added checkbox widget support 2025-10-12 13:58:23 +05:30
Byson94
74c6be70f7 feat: Added basic props support for all widgets 2025-10-12 13:58:23 +05:30
Byson94
8dd99376fd feat: Added semi dynamic support for rhai 2025-10-12 13:58:23 +05:30
Byson94
653bf17535 chore: run cargo fmt 2025-10-12 13:58:23 +05:30
Byson94
4b100d0073 feat: Added note section in error report 2025-10-12 13:58:23 +05:30
Byson94
5a0d267770 chore: ran cargo fmt to format code 2025-10-12 13:58:23 +05:30
Byson94
adb0442da5 feat: Improved err printing with help/hint and better UI 2025-10-12 13:58:23 +05:30
Byson94
674735ca65 feat: added dummy variables in rhai scope to fix eval 2025-10-12 13:58:23 +05:30
Byson94
ae31a360e9 feat: Added poll/listen IMPROVED error printing 2025-10-12 13:58:23 +05:30
Byson94
2188e9000e feat: Added polling (untested) 2025-10-12 13:58:23 +05:30
Byson94
1623d208f7 feat: starting the work on rhai signals 2025-10-12 13:58:22 +05:30
Byson94
18e9c91d69 fix: fixed license in Cargo.toml(s) 2025-10-12 13:58:22 +05:30
Byson94
2dd0850854 docs: Improved docs, added more files to fill in with content 2025-10-12 13:58:22 +05:30
Byson94
65f6e436f3 feat: Added support for buttons 2025-10-12 13:58:22 +05:30
Byson94
e07b15cd00 perf: Fixed calling get_window_rectangle in a loop 2025-10-12 13:58:22 +05:30
Byson94
7de16ec735 feat: added gtk scale support 2025-10-12 13:58:22 +05:30
Byson94
f18d4f1915 feat: Added better errors for rhai 2025-10-12 13:58:22 +05:30
Byson94
855393e137 feat: Added progress bar support 2025-10-12 13:58:22 +05:30
Byson94
7488330980 feat: Added center box widget support 2025-10-12 13:58:22 +05:30
Byson94
3ee3e1da5c feat: Removed ipc_server concept entirely 2025-10-12 13:58:22 +05:30
Byson94
ce48913e43 feat(style): Added scss/css styling support for rhai 2025-10-12 13:58:22 +05:30
Byson94
c9faec6c37 feat: Removed ipc server of rhai temporarely 2025-10-12 13:58:22 +05:30
Byson94
79041e5f40 feat: supported creating gtk labels in rhai 2025-10-12 13:58:22 +05:30
Byson94
5d03bdf561 fix: removed child of button and moved it to props 2025-10-12 13:58:22 +05:30
Byson94
8359d2191a fix: replaced ewwii.lua -> ewwii.rhai 2025-10-12 13:58:22 +05:30
Byson94
2f0874531e docs: Improved structure and moved installation to getting started 2025-10-12 13:58:22 +05:30
Byson94
b43cdabd46 Updated todo 2025-10-12 13:58:22 +05:30
Byson94
4aee3477a5 Added pretty_assertions to Cargo.toml 2025-10-12 13:58:22 +05:30
Byson94
2f027e5fa1 refactor: formatted code with cargo fmt 2025-10-12 13:58:22 +05:30
Byson94
b6df3aaf38 fix: Removed ParseError which was a duplicate of Error 2025-10-12 13:58:22 +05:30
Byson94
dace5d545a feat: fixed backend win opts to include fn for most structs 2025-10-12 13:58:22 +05:30
Byson94
0f07854917 fix: used resizable, stacking and backend_opts directly in win init 2025-10-12 13:58:22 +05:30
Byson94
8efaec8c6d feat: Added backend_options in windowdef struct 2025-10-12 13:58:22 +05:30
Byson94
ed5e388e7d feat(changelog): added new point in changelog 2025-10-12 13:58:22 +05:30
Byson94
11d1f3f1ce feat(ci): removed deno usage in gh-pages.yml 2025-10-12 13:58:22 +05:30
Byson94
0b5d69bf28 fix(ci): Fixed path (eww -> ewwii) 2025-10-12 13:58:22 +05:30
Byson94
636e926ece fix: Fixed monitoridentifier using index instead of numeric 2025-10-12 13:58:22 +05:30
Byson94
c57248363e feat: Added fromstr for anchorpoint 2025-10-12 13:58:22 +05:30
Byson94
4f88a2e554 feat: Added from fn to convert win_geo::coords to coords::Coords 2025-10-12 13:58:22 +05:30
Byson94
4b13c38c02 fix: Did many fixes that i forgot | 17 errors remaining 2025-10-12 13:58:22 +05:30
Byson94
c826822ea5 fix: removed .ok() from prop code in widget_def.rs 2025-10-12 13:58:22 +05:30
Byson94
ed204b9111 fix: used stm_enum_parse! in backend win opts instead of enum_parse! 2025-10-12 13:58:22 +05:30
Byson94
2f05508385 fix: fixed invalid use of diagerror & removed unnecessary arg in opts 2025-10-12 13:58:22 +05:30
Byson94
1108b33f7c fix: renamed window_geometry align_coord fn to work with app.rs 2025-10-12 13:58:22 +05:30
Byson94
31e8a8e302 feat: Removed open-many arg because we are pre-mature 2025-10-12 13:58:22 +05:30
Byson94
0eaa5f458b fix(warn): fixed all warnings related to imports 2025-10-12 13:58:22 +05:30
Byson94
d065e91626 feat(widgets): Made build_widget support both win_def and win_node 2025-10-12 13:58:22 +05:30
Byson94
eca6f74545 fix: changed fn in app.rs and fixed coords error 2025-10-12 13:58:22 +05:30
Byson94
39c08c8201 fix: removed old printstate and printgraph daemon commands 2025-10-12 13:58:22 +05:30
Byson94
01fbe1cf24 fix: removed semicolon form enum usage in backend win opts 2025-10-12 13:58:22 +05:30
Byson94
5d5d82b3d6 fix: added cstm_enum_parse to output errors in enumparserror 2025-10-12 13:58:22 +05:30
Byson94
d213e91260 docs: changed theme to navy (ayu -> navy) 2025-10-12 13:58:22 +05:30
Byson94
434cc3836a fix: naming error of anchor_point in window_geometry.rs 2025-10-12 13:58:22 +05:30
Byson94
3f3036c8cc fix: removed Eq derive from backend win opts to fix numwithunit error 2025-10-12 13:58:22 +05:30
Byson94
51a98c8e9f refactor: refactoring the code to fix the 75 errors that popped out of nowhere 2025-10-12 13:58:22 +05:30
Byson94
726e7c3ef1 fix: removed unennecessary impl in backend win opts 2025-10-12 13:58:22 +05:30
Byson94
cc7e931682 fix: fixed backend win opts and build_widget
fixed backend_window_options and build_widget codes. Removed unnecessary Error enum which used yuck internals and removed unnecessary children value in build_widget match.
2025-10-12 13:58:22 +05:30
Byson94
8c35ebdb2c fix: removed impl used by yuck in diag_error and fixed 1 compiler error 2025-10-12 13:58:22 +05:30
Byson94
00a4561a4b feat: added widget_definitions_helper file to help widget_definition codes 2025-10-12 13:58:22 +05:30
Byson94
181a5207e9 refactor: Refactored parse_geometry in window_initiator which was incomplete 2025-10-12 13:58:22 +05:30
Byson94
4814a552c7 refactor: Refactored backend_window_options and commented code related to yuck 2025-10-12 13:58:22 +05:30
Byson94
46b84fc6a8 fix: Fixed imports, lalrpop imports (not needed) and removed old yuck reference 2025-10-12 13:58:22 +05:30
Byson94
bc92ae8319 fix: uncommented error_handling_ctx to fix more compiler err 2025-10-12 13:58:22 +05:30
Byson94
d5e857f994 fix: Fixed more compiler errors (frgt what I fixed) 2025-10-12 13:58:22 +05:30
Byson94
bed41badbc fix: coord import in window_geometry 2025-10-12 13:58:15 +05:30
Byson94
c4e8692564 fix: Fixed compilation errors on WindowStacking 2025-10-12 13:58:15 +05:30
Byson94
0bef366685 fix(cargo): ewwii_tests warnings 2025-10-12 13:58:04 +05:30
Byson94
dc072e21af fix: Added proper imports (mod/use) to fix compilation errors 2025-10-12 13:58:04 +05:30
Byson94
fafb90d2d2 doc: updated changelog.md and added features and nt changes 2025-10-12 13:58:04 +05:30
Byson94
7222716957 docs(SUMMARY.md): improved sidebar layout 2025-10-12 13:58:04 +05:30
Byson94
ee172e50ed docs: Made few changes to configuration summary 2025-10-12 13:58:04 +05:30
Byson94
f6dc3dfc74 feat: Added & configuired backend_window_options a bit 2025-10-12 13:58:04 +05:30
Byson94
80b44941d0 feat: moved coords to window/ added window_geomtery from yuck/ crate 2025-10-12 13:58:04 +05:30
Byson94
73ecb64a9d feat: Added proper parse_geometry function in window_initiator.rs 2025-10-12 13:58:04 +05:30
Byson94
a8502b63b2 feat(window): Added window directory to rewrite yuck-win relation 2025-10-12 13:58:04 +05:30
Byson94
f120d12939 refactor: core window argument parser code to work with modern config parser (rhai) 2025-10-12 13:58:04 +05:30
Byson94
81abf8925b feat: adding back the files that were removed 2025-10-12 13:58:04 +05:30
Byson94
dc9aceb1cf docs(rebrand): change eww to ewwii 2025-10-12 13:57:50 +05:30
Byson94
e70685a166 fix(ci): Fixed ci to work with main branch as it used master 2025-10-12 13:57:50 +05:30
Byson94
850f322dff doc: updated docs (changed github repo) 2025-10-12 13:57:50 +05:30
Byson94
50d6e0dc1d feat(widgets): Added window.rs & fixed small compilation errs 2025-10-12 13:57:50 +05:30
Byson94
48bf133e46 feat(iirhai): Improved lable to accept props instead of string 2025-10-12 13:57:50 +05:30
Byson94
002f024926 feat(readme): Changed dependency status to ewwii's github | removed cargo_out.txt 2025-10-12 13:57:50 +05:30
Byson94
d86c0a8ba8 feat: Added test files to test build_gtk_widget() | rewriting widgets/ 2025-10-12 13:57:50 +05:30
Byson94
7603ea0371 fix: removed widget_def*.rs and fixed invalid root_widget var name in ewwii_config.rs 2025-10-12 13:57:50 +05:30
Byson94
c97ffbb99e fix(macro): Added gen_diagnostic_macro.rs to fix 15+ build errors 2025-10-12 13:57:50 +05:30
Byson94
dee74fbe03 fix(code): fixed about 10 errors due to dynval not imported 2025-10-12 13:57:50 +05:30
Byson94
4cf3a53b4f fix: Fixing more build errors that is caused by foundational scripts 2025-10-12 13:57:50 +05:30
Byson94
2b72e5ea0b fix: added root widget value to window def 2025-10-12 13:57:50 +05:30
Byson94
76c3de4ace fix(ipc): Fixed iirhai daemon to not start if it is already running 2025-10-12 13:57:50 +05:30
Byson94
a89fa7afb0 feat(nuke): removing unnecessary files which use yuck 2025-10-12 13:57:50 +05:30
Byson94
424c388ca3 style(comments): added/removed comments 2025-10-12 13:57:50 +05:30
Byson94
5f1859fff5 feat(kill): Killed state/ directory as well as files using yuck 2025-10-12 13:57:50 +05:30
Byson94
5af552721c feat(kill): killed widget_definitions which used yuck heavily 2025-10-12 13:57:50 +05:30
Byson94
3818cbc7cb feat(rm): removed yuck and simplexpr | note: you will get 444 errors 2025-10-12 13:57:50 +05:30
Byson94
31e5a695d7 fix: eww compile errors 2025-10-12 13:56:55 +05:30
Byson94
b7470581a9 feat(rename): renamed eww && eww_shared_util to be ewwii 2025-10-12 13:56:32 +05:30
Byson94
2a36c6ea6e feat(rename): Renamed eww_config to ewwii_config 2025-10-12 13:56:32 +05:30
Byson94
99fefbfb44 style(eww_config.rs): removed unnecessary whitespace 2025-10-12 13:56:32 +05:30
Byson94
f045f4eb38 feat(ipc): Improved iirhai daemon && added read/write 2025-10-12 13:56:32 +05:30
Byson94
2f130502cc feat(ipc): added mpsc to read messages from iirhai 2025-10-12 13:56:32 +05:30
Byson94
4b0f123313 feat(source): removing ALL yuck reference | NOTE: compilation will fail 2025-10-12 13:56:32 +05:30
Byson94
f69edff927 feat(ipc): Added ipc socket to paths.rs 2025-10-12 13:56:32 +05:30
Byson94
246535f3f1 fix(ipc): Fixed errors in ipc_server.rs related to iirhai 2025-10-12 13:56:32 +05:30
Byson94
efaab0c83b feat(ewwii): added functions to read iirhai ipc server 2025-10-12 13:56:32 +05:30
Byson94
669660a49c feat(ipc): added ipc daemon to iirhai 2025-10-12 13:56:32 +05:30
Byson94
3651e58631 feat(config): changed Cargo.toml files and added iirhai-daemon bin crate 2025-10-12 13:56:32 +05:30
Byson94
2ba90838e4 feat(iirhai): added ipc_manager (transpiler) 2025-10-12 13:56:32 +05:30
Byson94
72ded26393 style: just some comment ig 2025-10-12 13:56:32 +05:30
Byson94
111e0914b5 refactor: replacing yuck references with rhai 2025-10-12 13:56:32 +05:30
Byson94
2bfa24e72c rm: removed random duck/ crate 2025-10-12 13:56:32 +05:30
Byson94
bf61dd030a chore: replaced iirhai with the latest code from rhai-dlt 2025-10-12 13:56:32 +05:30
Byson94
592143137f chore(rename): rename iirhai crate 2025-10-12 13:55:58 +05:30
Byson94
999ad82e22 chore(license): rename and improve eww license 2025-10-12 13:55:30 +05:30
Byson94
1f95368168 chore: change few things and add comments 2025-10-12 13:54:49 +05:30
Byson94
bce8d05170 fix: SCREW IT! I AM DOING THIS COMMIT BECAUSE THERE WAS A MAJOR DEPENDENCY HELL 2025-10-12 13:54:49 +05:30
Byson94
7d72d4cc9c Create LICENSE 2025-10-12 13:54:49 +05:30
Byson94
3285b20d98 feat: add a basic rhai yuck replacement 2025-10-12 13:54:38 +05:30
Byson94
8cd92cc8c3 chore(rebrand): replace instance of eww with ewwii 2025-10-12 13:54:11 +05:30
Byson94
6a993f765e chore(credits): add credits in readme 2025-10-12 13:53:54 +05:30
Byson94
565fb896bd chore: mark things to change 2025-10-12 13:53:33 +05:30
Byson94
d6cff70840 chore(rebrand): replace eww with ewwii 2025-10-12 13:51:47 +05:30
Byson94
56f57e2ad6 fix: solve more cargo test issus 2025-10-12 13:24:39 +05:30
Byson94
961f6059b7 Merge branch 'main' into iidev 2025-10-12 13:15:06 +05:30
Byson94
2f34b6271e fix(ci): Satisfying doctest 2025-10-12 13:14:56 +05:30
Byson94
2c97bce3c0 Merge branch 'main' into iidev 2025-10-12 13:08:44 +05:30
Byson94
ed8cad319d fix: fixing doctest issues with auto_plugin macro 2025-10-12 13:08:20 +05:30
Byson94
8e39fe29ba fix(ci): using arch linux docker container 2025-10-12 12:59:08 +05:30
Byson94
1ee72b03b4 feat: adding panic handling 2025-10-12 12:48:31 +05:30
Byson94
92ad2b4a0d feat: added onkeypress and onkeyrelease props 2025-10-12 12:48:31 +05:30
Byson94
61d500142c fix: fixed old widget creeping in issue 2025-10-12 12:48:31 +05:30
Byson94
cf361902c2 fix(ci): fixing packages 2025-10-12 12:48:21 +05:30
Byson94
07d6293173 chore: cargo fmt 2025-10-11 15:35:17 +05:30
Byson94
c446321173 feat: making examples in readme look good 2025-10-11 15:34:14 +05:30
Byson94
17d59c1e15 fix(ci): Fixing indentation 2025-10-11 15:08:05 +05:30
Byson94
097f4c90fd fix(ci): Working on fixing the ci not running issue 2025-10-11 15:06:01 +05:30
Byson94
87670bfe52 chore: added info in changelog 2025-10-11 14:50:25 +05:30
Byson94
f5ba140830 chore: fixing ewwii_plugin_api versioning issue 2025-10-11 14:47:30 +05:30
Byson94
854051b42f feat: added max docs and 1 feat in ewwii_plugin_api 2025-10-11 14:46:56 +05:30
Byson94
076fc443d6 fix: fixing feature issue in widget_backend.rs 2025-10-11 09:43:54 +05:30
Byson94
43a5e10d72 style: fixing ewwii_plugin_api Cargo.toml style 2025-10-11 09:41:14 +05:30
Byson94
2f27e82a7b feat: improving the ewwii_plugin_api crate and fixing issues 2025-10-11 09:38:39 +05:30
Byson94
d2871d7bc9 feat: more api changes in ewwii_plugin_api 2025-10-10 22:30:07 +05:30
Byson94
4171eafeb9 feat: improving docs and comments on ewwii_plugin_api 2025-10-10 21:43:28 +05:30
Byson94
90a1ce0958 chore: fixing ewwii_plguin_api version issue 2025-10-10 20:04:54 +05:30
Byson94
3a75c5c306 feat: added the ability to modify widgets 2025-10-10 20:04:02 +05:30
Byson94
ebc5b6409b chore: added change in changelog 2025-10-10 19:10:55 +05:30
Byson94
e8314bfced fix(rhai): fixing poll/listen scope not available on all modules 2025-10-10 19:07:37 +05:30
Byson94
06abcc0905 chore: Updating Cargo.lock 2025-10-09 22:46:24 +05:30
Byson94
168af9eb81 fix: fixing ewwii_plugin_api version 2025-10-09 22:44:13 +05:30
Byson94
f57a8862f6 feat: expanding ewwii_plugin_api 2025-10-09 22:43:38 +05:30
Byson94
e6cbb52111 feat: added tools for exporting plugins 2025-10-09 22:37:29 +05:30
Byson94
238006dbb2 feat: added option to list all widget ids in plugin sys 2025-10-09 22:02:12 +05:30
Byson94
3366f6b10c feat: added documentation for ewwii_plugin_api 2025-10-09 09:20:40 +05:30
Byson94
8706ecab72 fix(ci): fixing dependencies on gtk4 2025-10-08 21:55:03 +05:30
Byson94
2ab8e2f981 feat: updating changelog 2025-10-08 21:39:55 +05:30
Byson94
958a220163 feat: added readme and docs to plugin_api 2025-10-08 20:25:00 +05:30
Byson94
62a5b55303 feat: fixing few errors and improving other stuff 2025-10-08 19:43:44 +05:30
Byson94
4ed0cea596 chore: cargo fmt 2025-10-08 18:35:27 +05:30
Byson94
c7676f9e63 fix: fixing issue templates 2025-10-08 18:35:01 +05:30
Byson94
8cbd780479 feat: Updating changelog 2025-10-08 18:25:35 +05:30
Byson94
c999b53691 feat: getting rhai mod working via plugin 2025-10-08 18:06:34 +05:30
Byson94
af4f1a5533 feat: got library system loading working 2025-10-07 20:34:44 +05:30
Byson94
2904ba74dc wip: bin level plugin system 2025-10-07 18:56:34 +05:30
Byson94
d97a3af401 fix: fixed focusable not working 2025-10-05 17:32:50 +05:30
Byson94
c8d2d7ce87 feat: adding the release date in changelog 2025-10-04 19:54:55 +05:30
Byson94
b0e5224baa fix: fixing minor issues 2025-10-03 19:54:51 +05:30
Byson94
34546e5528 feat: removed centerbox widget 2025-10-03 18:25:04 +05:30
Byson94
7cb3a4981e GREATEST FEAT: Achived sticky windows in x11 2025-10-02 20:48:48 +05:30
Byson94
85a11e4195 fix: fixed gif images not working 2025-10-02 18:05:21 +05:30
Byson94
8d82b68ab5 fix: did a simple fix over the scale drag issue 2025-10-02 16:58:34 +05:30
Byson94
c778c98d64 chore: Cargo.lock update 2025-10-02 14:28:12 +05:30
Byson94
44cbb43472 feat: added deprecated section in changelog 2025-10-02 10:54:48 +05:30
Byson94
107ccce58a feat: added information about alpha release in changelog 2025-10-02 10:46:29 +05:30
Byson94
8fb4599a06 fix: fixing all compiler warnings 2025-10-02 10:25:52 +05:30
Byson94
db7522970f feat: fixed image wdgt and added icon wdgt 2025-10-02 10:22:24 +05:30
Byson94
0b8253b8e4 fix: no classes to window to prevent x11 issues 2025-10-02 09:37:11 +05:30
Byson94
8f38a35479 feat: fixing warnings & messin with x11 2025-10-02 08:57:04 +05:30
Byson94
5b5c805bb0 fix(x11): fixing widget not realized before getting surface 2025-10-01 20:41:59 +05:30
Byson94
6fcb411d80 fix: fixing the ewwii window close order 2025-10-01 19:09:56 +05:30
Byson94
d8ca70b5cf feat: fixing application loop issues by doing manual one 2025-10-01 18:07:18 +05:30
Byson94
7a6cdc9460 feat: fixing very single gtk4 migration error! yippee! 2025-10-01 16:53:22 +05:30
Byson94
eb0d034a46 fix: fixing one of the last few err (i think so) 2025-10-01 15:05:38 +05:30
Byson94
de3c9e7337 fix: even more fixing of gtk4 errors 2025-10-01 14:02:01 +05:30
Byson94
bb86520f43 fix: fixing the drag thingy in eventbox 2025-10-01 11:00:48 +05:30
Byson94
2e3626069e Merge branch 'main' into gtk4_migration 2025-10-01 10:10:52 +05:30
Byson94
b395d71a3c fix: fixing broken wifi gui manager screenshot 2025-10-01 10:10:39 +05:30
Byson94
f7843cb5b2 fix: fixing more errors that i cant name of 2025-10-01 10:06:35 +05:30
Byson94
9b0e042add fix: fixing many many more gtk4 migration errs 2025-10-01 10:01:10 +05:30
Byson94
5bde8145a2 fix: added controlflow in gif render part 2025-09-30 20:48:50 +05:30
Byson94
99ddaec57a fix: fixing more gtk4 related err 2025-09-30 20:45:27 +05:30
Byson94
eb46ac3d39 feat: added EventControllerKey in widget_definition.rs 2025-09-30 20:41:12 +05:30
Byson94
8aead373d1 fix: fixing gtk_layer_shell naming issues 2025-09-30 20:38:50 +05:30
Byson94
c79d79e4ce fix: fixing glib weak reference issues 2025-09-30 20:36:19 +05:30
Byson94
090dc09d39 fix: fixing timeouts in eventbox being a borrow 2025-09-30 20:03:04 +05:30
Byson94
9432deb0b9 rewrite: rewriting button and event_box widgets 2025-09-30 19:55:20 +05:30
Byson94
cd1dd82978 feat: rewriting eventbox to work with gtk4 2025-09-30 17:48:15 +05:30
Byson94
f9fafb844b fix: fixing even more errors in widget_definition 2025-09-30 13:36:30 +05:30
Byson94
bdac4492ac fix: fixing glib::clone! in widget_definitions.rust 2025-09-30 13:21:58 +05:30
Byson94
78e1f711c4 fix: fixed connect_monitor_added function 2025-09-30 13:20:30 +05:30
Byson94
9cfc88b96f fix: fixed style_provider reelated err 2025-09-30 12:29:30 +05:30
Byson94
c53ca529a8 fix: some more fix and cargo fmt 2025-09-30 12:11:15 +05:30
Byson94
b8e5bae524 fix: fixing more minor errors 2025-09-30 12:02:20 +05:30
Byson94
904e1f9dc7 feat: fixing more errors and following gtk4 model 2025-09-30 11:54:13 +05:30
Byson94
655aedc478 feat: stubbing widget creation logic for easier migration 2025-09-30 10:45:51 +05:30
Byson94
1d69805fac wip: More progress in gtk4 migration 2025-09-30 10:22:14 +05:30
Byson94
bf75ef1c36 wip: migrating to gtk4 2025-09-29 17:21:38 +05:30
55 changed files with 4856 additions and 2212 deletions

View File

@@ -7,9 +7,9 @@ body:
attributes:
label: Checklist before submitting an issue
options:
- label: I have searched through the existing [closed and open issues](https://github.com/elkowar/eww/issues?q=is%3Aissue) for eww and made sure this is not a duplicate
- label: I have searched through the existing [closed and open issues](https://github.com/Ewwii-sh/ewwii/issues?q=is%3Aissue) for ewwii and made sure this is not a duplicate
required: true
- label: I have specifically verified that this bug is not a common [user error](https://github.com/elkowar/eww/issues?q=is%3Aissue+label%3Ano-actual-bug+is%3Aclosed)
- label: I have specifically verified that this bug is not a common [user error](https://github.com/Ewwii-sh/ewwii/issues?q=is%3Aissue+label%3Ano-actual-bug+is%3Aclosed)
required: true
- label: I am providing as much relevant information as I am able to in this bug report (Minimal config to reproduce the issue for example, if applicable)
required: true
@@ -40,6 +40,6 @@ body:
- type: textarea
attributes:
label: "Platform and environment"
description: "Does this happen on wayland, X11, or on both? What WM/Compositor are you using? Which version of eww are you using? (when using a git version, optimally provide the exact commit ref)."
description: "Does this happen on wayland, X11, or on both? What WM/Compositor are you using? Which version of ewwii are you using? (when using a git version, optimally provide the exact commit ref)."
validations:
required: true

View File

@@ -12,7 +12,7 @@ body:
- type: textarea
attributes:
label: "Proposed configuration syntax"
description: "If the feature you are requesting would add or change something to the Eww configuration, please provide an example for how the feature could be used."
description: "If the feature you are requesting would add or change something to the Ewwii configuration, please provide an example for how the feature could be used."
validations:
required: false
- type: textarea

View File

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

View File

@@ -1,45 +1,56 @@
name: build
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
push:
branches:
- main
pull_request:
branches:
- main
env:
CARGO_TERM_COLOR: always
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev libdbusmenu-gtk3-dev
build:
runs-on: ubuntu-latest
container:
image: archlinux:latest
- uses: actions/checkout@v4
steps:
- name: Install dependencies
run: |
pacman -Syu --noconfirm
pacman -S --noconfirm base-devel gtk4 gtk4-layer-shell pkgconf git
- name: Setup rust
uses: dtolnay/rust-toolchain@stable
with:
components: clippy,rustfmt
- uses: actions/checkout@v4
- name: Load rust cache
uses: Swatinem/rust-cache@v2
- name: Setup rust
uses: dtolnay/rust-toolchain@stable
with:
components: clippy,rustfmt
- name: Setup problem matchers
uses: r7kamura/rust-problem-matchers@v1
- name: Load rust cache
uses: Swatinem/rust-cache@v2
- name: Check formatting
run: cargo fmt -- --check
- name: Check with default features
run: cargo check
- name: Setup problem matchers
uses: r7kamura/rust-problem-matchers@v1
- name: Run tests
run: cargo test
- name: Check formatting
run: cargo fmt -- --check
- name: Check x11 only
run: cargo check --no-default-features --features=x11
- name: Check wayland only
run: cargo check --no-default-features --features=wayland
- name: Check no-backend
run: cargo check --no-default-features
- name: Check with default features
run: cargo check
- name: Run tests
run: cargo test
- name: Check x11 only
run: cargo check --no-default-features --features=x11
- name: Check wayland only
run: cargo check --no-default-features --features=wayland
- name: Check no-backend
run: cargo check --no-default-features

View File

@@ -5,6 +5,123 @@ 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.
- `graph` widget back.
### Changed
- `v4_18` feature flag for to the `gtk4` crate to enable newer APIs.
- `content_fit` property to image widget.
- `can_shrink` property to image widget.
- `mutations` property to localsignal.
- `eval_ignore` property to all widgets.
- Touch support to scale widget.
### Fixed
- `clockwise` property not working on circular_progress.
### Removed
- icon widget.
## [0.3.1] - 2025-11-01
## Fixed
- Circular progress bar not updating dynamically.
- LocalSignal values not getting transformed to suite property type.
- LocalBind not finding properties of range subclasses.
## [0.3.0] - 2025-11-01
### Added
- `localsignal` signal for fast and cheap property update.
- `localbind` utility for binding `localsignal` to a widget property.
- `onkeypress` property to eventbox.
- `onkeyrelease` property to eventbox.
- Selection of dash as shell if it installed.
- `--with-plugin` flag for `daemon` command.
- `register_function` API in ewwii_plugin_api for registering functions that rhai can call to.
- `slib` rhai module for calling functions registered via `register_function`.
- `orientation` property to eventbox.
- `spacing` property to eventbox.
- `space_evenly` property to eventbox.
- An advanced widget named `flowbox`.
- `focusable` property to all widget.
- `widget_name` property to all widget.
- `lifetime` flag for update command.
- `circular-progress` widget back.
### Fixed
- Old widget creeping into new ones after hot reload.
- Ewwii defaulting to default gtk4 theme. #9
- Few issues with eventbox drop target.
- Poll/Listen variables not exposed in other modules on first launch.
- Not able to define poll/listen variables in other files.
- Overlay and Tooltip widgets not being reactive.
## [0.3.0-beta] - 2025-10-11
### Added
- Support for binary level plugins.
- `set-plugin` command to load shared libraries (i.e plugins).
- `ewwii_plugin_api` crate for building ewwii plugins easily.
### Changed
- The name of `slider` widget to `scale`.
### Fixed
- `focusable` property not working issue.
- Poll/Listen variable scope not accessable to all modules.
## [0.3.0-alpha] - 2025-10-04
### Added
- GTK4 support.
- `can_target` boolean property for all widgets.
- `icon` widget which always preserves a scaling of 1:1.
- Full native wayland compatibility (x11 compatibility has decreased).
### Changed
- The way dynamic updates are handled.
- Window positioning logic.
- X11 communication and x11 based window handling.
- Daemon GTK main loop.
- Application of sticky and stacking propery of window on X11.
- The full implementation of eventbox widget.
- GTK controller/signal handling.
- Initialization logic of window.
- Overlay widget dynamic post-creation re-creation logic (bugs are expected).
### Removed
- `show_details` property of calendar.
- `angle` property from label.
- `icon_size` property from image widget.
- `same_size` property from stack widget (box is a replacement).
- `icon_name` from `image` widget as a result of the introduction of `icon` widget.
- `transform`, `graph`, and `circular_progress` widget (temporarily).
- Application of css class on the window.
- Legacy GTK3 related code.
- Centerbox widget.
## [0.2.0] - 2025-09-29
### Added

681
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +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" }
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"
@@ -24,7 +26,7 @@ derive_more = { version = "1", features = [
extend = "1.2"
futures = "0.3.30"
grass = "0.13.4"
gtk = "0.18.1"
gtk4 = { version = "0.10.3", features = ["v4_18", "v4_8"] }
itertools = "0.13.0"
libc = "0.2"
log = "0.4"
@@ -34,7 +36,7 @@ once_cell = "1.19"
pretty_assertions = "1.4.0"
pretty_env_logger = "0.5.0"
regex = "1.10.5"
rhai = "1.22.2"
rhai = "1.23.6"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
simple-signal = "1.1"
@@ -44,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"

View File

@@ -1,4 +1,5 @@
[![dependency status](https://deps.rs/repo/github/byson94/ewwii/status.svg)](https://deps.rs/repo/github/byson94/ewwii)
[![docs link](https://img.shields.io/badge/documentation-link-blue)](https://ewwii-sh.github.io/docs)
# Ewwii
@@ -8,18 +9,23 @@ Elkowars Wacky Widgets Imporved Interface is a fork of Elkowars Wacky Widgets wh
## 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>
![Example 1](./examples/ewwii-bar/ewwii-bar.png)
| Project | Preview |
|---------|---------|
| **Basic Bar**<br>[- View Example](./examples/ewwii-bar) | [![Basic Bar](./examples/ewwii-bar/ewwii-bar.png)](./examples/ewwii-bar) |
| **Data Structures**<br>[- View Example](./examples/data-structures) | [![Data Structures](./examples/data-structures/data-structures-preview.png)](./examples/data-structures) |
| **Wi-Fi GUI Template**<br>[- View on GitHub](https://github.com/Ewwii-sh/ewifi_gui_template) | ![Wi-Fi GUI Template](https://raw.githubusercontent.com/Ewwii-sh/ewifi_gui_template/main/.github/wifi_manager_template.png) |
| **Obsidian Bar Template**<br>[- View on GitHub](https://github.com/Ewwii-sh/obsidian-bar) | [![Obsidian Bar](https://raw.githubusercontent.com/Ewwii-sh/obsidian-bar/main/.github/screenshot.png)](https://github.com/Ewwii-sh/obsidian-bar) |
| **Binary Dots by [@BinaryHarbinger](https://github.com/BinaryHarbinger)**<br>[- View on GitHub](https://github.com/BinaryHarbinger/binarydots/) | [![Binary Dots](https://raw.githubusercontent.com/BinaryHarbinger/binarydots/main/preview/Desktop.png)](https://github.com/BinaryHarbinger/binarydots)
| **Astatine Dots (Linux Rice with Ewwii)**<br>[- View on GitHub](https://github.com/Ewwii-sh/astatine-dots) | [![Astatine Dots](https://github.com/user-attachments/assets/f028ca1f-e403-476d-a7d9-cadce47691b7)](https://github.com/Ewwii-sh/astatine-dots) |
- Data structures [CLICK TO SEE](./examples/data-structures) <br>
![Example 2](./examples/data-structures/data-structures-preview.png)
## Features
## Templates
- A wifi gui template [CLICK TO SEE](https://github.com/Ewwii-sh/ewifi_gui_template) <br>
<img src="https://raw.githubusercontent.com/Ewwii-sh/ewwii/main/docs/src/images/wifi_manager_template.png" width="400" />
- Powered by Gtk4
- Supports Hot reload
- Extensibility via plugins and rhai modules
- X11 + Wayland support
## Contribewwtiing

View File

@@ -1,6 +1,6 @@
[package]
name = "ewwii"
version = "0.2.0"
version = "0.4.0"
authors = ["byson94 <byson94wastaken@gmail.com>"]
description = "Widgets for everyone made better!"
license = "GPL-3.0-or-later"
@@ -11,17 +11,17 @@ edition = "2021"
[features]
default = ["x11", "wayland"]
x11 = ["gdkx11", "x11rb"]
wayland = ["gtk-layer-shell"]
x11 = ["gdk4-x11", "x11rb"]
wayland = ["gtk4-layer-shell"]
[dependencies]
shared_utils.workspace = true
rhai_impl.workspace = true
ewwii_plugin_api.workspace = true
gtk-layer-shell = { version = "0.8.1", optional = true, features=["v0_6"] }
gdkx11 = { version = "0.18", optional = true }
gtk4-layer-shell = { version = "0.6.3", optional = true }
gdk4-x11 = { version = "0.10.1", optional = true }
x11rb = { version = "0.13.1", features = ["randr"], optional = true }
gdk-sys = "0.18.0"
grass.workspace = true
thiserror.workspace = true
@@ -35,7 +35,7 @@ derive_more.workspace = true
extend.workspace = true
futures.workspace = true
smart-default.workspace = true
gtk.workspace = true
gtk4.workspace = true
itertools.workspace = true
log.workspace = true
nix = { workspace = true, features = ["process", "fs", "signal"] }
@@ -49,7 +49,10 @@ simple-signal.workspace = true
tokio = { workspace = true, features = ["full"] }
unescape.workspace = true
wait-timeout.workspace = true
rhai.workspace = true
rhai = { workspace = true, features = ["internals"] }
shell-words.workspace = true
# Plugin loading
libloading = "0.8.9"
[dev-dependencies]
pretty_assertions.workspace = true

View File

@@ -1,13 +1,13 @@
use crate::diag_error::DiagError;
use crate::plugin::PluginRequest;
use crate::{
daemon_response::DaemonResponseSender,
display_backend::DisplayBackend,
error_handling_ctx,
gtk::prelude::{
ContainerExt, CssProviderExt, GtkWindowExt, MonitorExt, StyleContextExt, WidgetExt,
gtk4::prelude::{
Cast, CastNone, DisplayExt, GtkWindowExt, ListModelExt, MonitorExt, NativeExt, ObjectExt,
StyleContextExt, WidgetExt,
},
paths::EwwiiPaths,
widgets::window::Window,
// dynval::DynVal,
widgets::{
build_widget::build_gtk_widget, build_widget::WidgetInput,
@@ -23,16 +23,16 @@ use crate::{
*,
};
use anyhow::{anyhow, bail};
use codespan_reporting::files::Files;
use ewwii_plugin_api as epapi;
use gdk::Monitor;
use glib::ObjectExt;
use gtk::{gdk, glib};
use gtk4::Window;
use gtk4::{gdk, glib};
use itertools::Itertools;
use once_cell::sync::Lazy;
use once_cell::sync::OnceCell;
use rhai::Dynamic;
use rhai_impl::ast::WidgetNode;
use rhai_impl::parser::ParseConfig;
use serde::{de::Error as SerdeError, Deserialize, Deserializer};
use shared_utils::Span;
use std::{
cell::{Cell, RefCell},
collections::{HashMap, HashSet},
@@ -42,6 +42,12 @@ use std::{
};
use tokio::sync::mpsc::UnboundedSender;
static ACTIVE_PLUGIN: OnceCell<libloading::Library> = OnceCell::new();
fn set_active_plugin(lib: libloading::Library) -> Result<()> {
ACTIVE_PLUGIN.set(lib).map_err(|_| anyhow!("Plugin already set"))
}
/// A command for the ewwii daemon.
/// While these are mostly generated from ewwii CLI commands (see [`opts::ActionWithServer`]),
/// they may also be generated from other places internally.
@@ -79,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 {
@@ -93,6 +104,10 @@ pub enum DaemonCommand {
print: bool,
sender: DaemonResponseSender,
},
SetPlugin {
file_path: String,
sender: DaemonResponseSender,
},
}
/// An opened window.
@@ -118,17 +133,17 @@ impl std::fmt::Debug for EwwiiWindow {
impl EwwiiWindow {
/// Close the GTK window and disconnect the destroy event-handler.
///
/// You need to make sure that the scope get's properly cleaned from the state graph
/// and that script-vars get cleaned up properly
/// You need to make sure that the window gets propery cleaned from gtk4!
pub fn close(self) {
log::info!("Closing gtk window {}", self.name);
self.gtk_window.close();
for handler_id_opt in [self.destroy_event_handler_id, self.delete_event_handler_id] {
if let Some(handler_id) = handler_id_opt {
self.gtk_window.disconnect(handler_id);
}
}
self.gtk_window.close();
}
}
@@ -143,7 +158,8 @@ pub struct App<B: DisplayBackend> {
/// Window names that are supposed to be open, but failed.
/// When reloading the config, these should be opened again.
pub failed_windows: HashSet<String>,
pub css_provider: gtk::CssProvider,
pub css_provider: gtk4::CssProvider,
pub reloading: bool,
/// Sender to send [`DaemonCommand`]s
pub app_evt_send: UnboundedSender<DaemonCommand>,
@@ -155,11 +171,14 @@ pub struct App<B: DisplayBackend> {
pub widget_reg_store: Rc<Mutex<Option<WidgetRegistry>>>,
// The cached store of poll/listen handlers
pub pl_handler_store: Option<rhai_impl::updates::ReactiveVarStore>,
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>>,
pub paths: EwwiiPaths,
pub gtk_main_loop: gtk4::glib::MainLoop,
pub phantom: PhantomData<B>,
}
@@ -180,8 +199,18 @@ async fn wait_for_monitor_model() {
let display = gdk::Display::default().expect("could not get default display");
let start = std::time::Instant::now();
loop {
let all_monitors_set = (0..display.n_monitors())
.all(|i| display.monitor(i).and_then(|monitor| monitor.model()).is_some());
let monitors_model = display.monitors();
let n_monitors = monitors_model.n_items();
let all_monitors_set = (0..n_monitors).all(|i| {
if let Some(obj) = monitors_model.item(i) {
// Downcast GObject to Monitor
let monitor: Monitor = obj.downcast().unwrap();
monitor.model().is_some()
} else {
false
}
});
if all_monitors_set {
break;
}
@@ -207,7 +236,7 @@ impl<B: DisplayBackend> App<B> {
match event {
DaemonCommand::NoOp => {}
DaemonCommand::OpenInspector => {
gtk::Window::set_interactive_debugging(true);
gtk4::Window::set_interactive_debugging(true);
}
DaemonCommand::ReloadConfigAndCss(sender) => {
// Wait for all monitor models to be set. When a new monitor gets added, this
@@ -216,7 +245,11 @@ impl<B: DisplayBackend> App<B> {
wait_for_monitor_model().await;
let mut errors = Vec::new();
let config_result = config::read_from_ewwii_paths(&self.paths);
let mut parser_ref = self.config_parser.borrow_mut();
let config_result = config::read_from_ewwii_paths(&self.paths, &mut *parser_ref);
drop(parser_ref);
if let Err(e) = config_result.and_then(|new_config| self.load_config(new_config)) {
errors.push(e)
}
@@ -320,17 +353,22 @@ impl<B: DisplayBackend> App<B> {
sender.send_success(output)?
}
DaemonCommand::ShowState(sender) => {
if let Some(maybe_store) = &self.pl_handler_store {
let output = format!("{:#?}", maybe_store.read().unwrap());
sender.send_success(output)?
} else {
sender.send_failure(
"The poll/listen handler store doesn't exist or is empty".to_string(),
)?
}
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())?,
};
@@ -353,6 +391,12 @@ impl<B: DisplayBackend> App<B> {
Err(e) => sender.send_failure(e.to_string())?,
};
}
DaemonCommand::SetPlugin { file_path, sender } => {
match self.set_ewwii_plugin(file_path) {
Ok(_) => sender.send_success(String::from("OK"))?,
Err(e) => sender.send_failure(e.to_string())?,
}
}
}
Ok(())
}
@@ -364,7 +408,7 @@ impl<B: DisplayBackend> App<B> {
for (_, window) in self.open_windows.drain() {
window.close();
}
gtk::main_quit();
self.gtk_main_loop.quit();
let _ = crate::application_lifecycle::send_exit();
}
@@ -379,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());
@@ -393,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();
}
@@ -458,7 +505,6 @@ impl<B: DisplayBackend> App<B> {
let monitor = get_gdk_monitor(initiator.monitor.clone())?;
let mut ewwii_window = initialize_window::<B>(&initiator, monitor, root_widget)?;
ewwii_window.gtk_window.style_context().add_class(window_name);
// Start the poll/listen only once per startup
// at the start, the open_windows will be empty because
@@ -471,23 +517,35 @@ impl<B: DisplayBackend> App<B> {
// so I guess that I will just let it stay right here.
let config_path = self.paths.get_rhai_path();
let compiled_ast = self.ewwii_config.get_owned_compiled_ast();
let mut stored_parser = rhai_impl::parser::ParseConfig::new();
stored_parser.set_opt_level(get_opt_level_from(
self.rt_engine_config.optimization_level.unwrap_or(1),
));
if self.open_windows.is_empty() {
{
let mut stored_parser = self.config_parser.borrow_mut();
stored_parser.set_opt_level(get_opt_level_from(
self.rt_engine_config.optimization_level.unwrap_or(1),
));
}
let stored_parser_clone = self.config_parser.clone();
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();
let store = rhai_impl::updates::handle_state_changes(
// Will automatically mutate pl_handler_store
rhai_impl::updates::handle_state_changes(
self.ewwii_config.get_root_node()?.as_ref(),
tx,
self.pl_handler_store.clone(),
);
self.pl_handler_store = Some(store.clone());
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();
@@ -507,11 +565,13 @@ 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(),
Some(&mut stored_parser),
compiled_ast_ref.as_deref(),
&mut *parser_rc,
)
.await
{
@@ -539,18 +599,20 @@ impl<B: DisplayBackend> App<B> {
} else {
// else, just update the window from the current store.
let widget_reg_store = self.widget_reg_store.clone();
let store = self
.pl_handler_store
.clone()
.expect("Failed to clone poll listen handler store");
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(),
Some(&mut stored_parser),
compiled_ast_ref.as_deref(),
&mut *parser_rc,
)
.await
{
@@ -580,8 +642,6 @@ impl<B: DisplayBackend> App<B> {
// This callback is triggered in 2 cases:
// - When the monitor of this window gets disconnected
// - When the window is closed manually.
// We don't distinguish here and assume the window should be reopened once a monitor
// becomes available again
move |auto_reopen| {
let (response_sender, _) = daemon_response::create_pair();
let command = DaemonCommand::CloseWindows {
@@ -599,10 +659,10 @@ impl<B: DisplayBackend> App<B> {
// handling users close request
ewwii_window.delete_event_handler_id =
Some(ewwii_window.gtk_window.connect_delete_event({
Some(ewwii_window.gtk_window.connect_close_request({
let handler = gtk_close_handler.clone();
let closed_by_user = closed_by_user.clone();
move |_, _| {
move |_| {
handler(false); // -- false: don't reopen window to respect users intent
closed_by_user.set(true);
glib::Propagation::Proceed
@@ -669,53 +729,134 @@ 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<()> {
if let Err(err) = self.css_provider.load_from_data(css.as_bytes()) {
static PATTERN: Lazy<regex::Regex> =
Lazy::new(|| regex::Regex::new(r"[^:]*:(\d+):(\d+)(.*)$").unwrap());
let nice_error_option: Option<_> = (|| {
let captures = PATTERN.captures(err.message())?;
let line = captures.get(1).unwrap().as_str().parse::<usize>().ok()?;
let msg = captures.get(3).unwrap().as_str();
let db = error_handling_ctx::FILE_DATABASE.read().ok()?;
let line_range = db.line_range(file_id, line - 1).ok()?;
let span = Span(line_range.start, line_range.end - 1, file_id);
Some(DiagError(gen_diagnostic!(msg, span)))
})();
match nice_error_option {
Some(error) => Err(anyhow!(error)),
None => Err(anyhow!("CSS error: {}", err.message())),
/// 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");
}
}
} else {
Ok(())
}
Ok(())
}
/// 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();
@@ -724,39 +865,41 @@ impl<B: DisplayBackend> App<B> {
bail!("The configuration file `{}` does not exist", config_path.display());
}
let mut reeval_parser = ParseConfig::new();
let mut reeval_parser = self.config_parser.borrow_mut();
let rhai_code = reeval_parser.code_from_file(&config_path)?;
let mut scope = ParseConfig::initial_poll_listen_scope(&rhai_code)?;
if let Some(maybe_all_vars) = &self.pl_handler_store {
let all_vars = maybe_all_vars.read().unwrap().clone();
let all_vars = self.pl_handler_store.read().unwrap().clone();
for (name, val) in all_vars {
scope.set_value(name.clone(), Dynamic::from(val.clone()));
}
for (name, val) in all_vars {
scope.set_value(name.clone(), Dynamic::from(val.clone()));
}
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.
// ---
// This is esstentially storing the inject variables
// in the poll/listen variable store (or the `pl_handler_store` in self)
if should_preserve_state {
if let Some(maybe_store) = &self.pl_handler_store {
maybe_store.write().unwrap().insert(name, val);
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(),
)?;
@@ -777,14 +920,14 @@ impl<B: DisplayBackend> App<B> {
let compiled_ast = self.ewwii_config.get_owned_compiled_ast();
let config_path = self.paths.get_rhai_path();
let mut reeval_parser = ParseConfig::new();
let mut reeval_parser = self.config_parser.borrow_mut();
let rhai_code = reeval_parser.code_from_file(&config_path)?;
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))?;
@@ -799,6 +942,92 @@ impl<B: DisplayBackend> App<B> {
Ok(())
}
pub fn set_ewwii_plugin(&mut self, file_path: String) -> Result<()> {
if ACTIVE_PLUGIN.get().is_some() {
anyhow::bail!("A plugin is already loaded");
}
let lib = unsafe {
libloading::Library::new(file_path)
.map_err(|e| anyhow!("Failed to load plugin: {}", e))?
};
let (tx, rx): (
std::sync::mpsc::Sender<PluginRequest>,
std::sync::mpsc::Receiver<PluginRequest>,
) = std::sync::mpsc::channel();
unsafe {
// Each plugin exposes: extern "C" fn create_plugin() -> Box<dyn Plugin>
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
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();
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(())
}
}
#[derive(Deserialize, Debug)]
@@ -847,7 +1076,7 @@ where
fn initialize_window<B: DisplayBackend>(
window_init: &WindowInitiator,
monitor: Monitor,
root_widget: gtk::Widget,
root_widget: gtk4::Widget,
) -> Result<EwwiiWindow> {
let monitor_geometry = monitor.geometry();
let (actual_window_rect, x, y) = match window_init.geometry {
@@ -861,47 +1090,71 @@ fn initialize_window<B: DisplayBackend>(
format!("monitor {} is unavailable", window_init.monitor.clone().unwrap())
})?;
window.set_title(&format!("Ewwii - {}", window_init.name));
window.set_position(gtk::WindowPosition::None);
window.set_gravity(gdk::Gravity::Center);
window.set_title(Some(&format!("Ewwii - {}", window_init.name)));
// window.set_position(gtk4::WindowPosition::None);
// window.set_gravity(gdk::Gravity::Center);
if let Some(actual_window_rect) = actual_window_rect {
window.set_size_request(actual_window_rect.width(), actual_window_rect.height());
window.set_default_size(actual_window_rect.width(), actual_window_rect.height());
}
window.set_decorated(false);
window.set_skip_taskbar_hint(true);
window.set_skip_pager_hint(true);
// window.set_skip_taskbar_hint(true);
// window.set_skip_pager_hint(true);
// run on_screen_changed to set the visual correctly initially.
on_screen_changed(&window, None);
window.connect_screen_changed(on_screen_changed);
// on_screen_changed(&window, None);
// window.connect_screen_changed(on_screen_changed);
window.add(&root_widget);
window.set_child(Some(&root_widget));
window.realize();
gtk4::prelude::WidgetExt::realize(&window);
#[cfg(feature = "x11")]
if B::IS_X11 {
if let Some(geometry) = window_init.geometry {
let _ = apply_window_position(geometry, monitor_geometry, &window);
let (conn, _) = x11rb::rust_connection::RustConnection::connect(None)?;
let x11_conn = Rc::new(conn);
let gdk_surface =
window.surface().context("Couldn't get gdk window from gtk window")?;
let win_xid = gdk_surface
.downcast_ref::<gdk4_x11::X11Surface>()
.context("Failed to get x11 window for gtk window")?
.xid() as u32;
use x11rb::protocol::xproto::*;
x11_conn
.clone()
.change_window_attributes(
win_xid,
&ChangeWindowAttributesAux::new().event_mask(EventMask::STRUCTURE_NOTIFY),
)
.unwrap();
let _ = apply_window_position(x11_conn.clone(), geometry, monitor_geometry, &window);
if window_init.backend_options.x11.window_type
!= crate::window::backend_window_options::X11WindowType::Normal
{
let last_pos = Rc::new(RefCell::new(None));
window.connect_configure_event({
let last_pos = last_pos.clone();
move |window, _| {
let gdk_window = window.window().unwrap();
let current_origin = gdk_window.origin();
let window_clone = window.clone();
let conn_clone = x11_conn.clone();
let mut last = last_pos.borrow_mut();
if Some((current_origin.1, current_origin.2)) != *last {
*last = Some((current_origin.1, current_origin.2));
let _ = apply_window_position(geometry, monitor_geometry, window);
use x11rb::connection::Connection;
glib::MainContext::default().spawn_local(async move {
loop {
if let Ok(event) = conn_clone.poll_for_event() {
if let Some(x11rb::protocol::Event::ConfigureNotify(_ev)) = event {
let _ = apply_window_position(
conn_clone.clone(),
geometry,
monitor_geometry,
&window_clone,
);
}
}
false
glib::timeout_future(std::time::Duration::from_millis(10)).await;
}
});
}
@@ -909,7 +1162,7 @@ fn initialize_window<B: DisplayBackend>(
display_backend::set_xprops(&window, monitor, window_init)?;
}
window.show_all();
window.show();
Ok(EwwiiWindow {
name: window_init.name.clone(),
@@ -919,35 +1172,25 @@ fn initialize_window<B: DisplayBackend>(
})
}
use rhai_impl::parser::ParseConfig;
async fn generate_new_widgetnode(
all_vars: &HashMap<String, String>,
code_path: &Path,
compiled_ast: Option<&rhai::AST>,
parser: Option<&mut ParseConfig>,
parser: &mut ParseConfig,
) -> Result<WidgetNode> {
let mut owned_parser;
let reeval_parser: &mut ParseConfig = match parser {
Some(p) => p,
None => {
owned_parser = ParseConfig::new();
&mut owned_parser
}
};
let rhai_code = reeval_parser.code_from_file(&code_path)?;
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)?;
for (name, val) in all_vars {
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 =
reeval_parser.eval_code_with(&rhai_code, Some(scope), compiled_ast, code_path.to_str())?;
parser.eval_code_with(&rhai_code, Some(scope), compiled_ast, code_path.to_str())?;
Ok(new_root_widget)
}
@@ -964,29 +1207,40 @@ fn get_opt_level_from(n: u8) -> rhai::OptimizationLevel {
/// Apply the provided window-positioning rules to the window.
#[cfg(feature = "x11")]
fn apply_window_position(
conn: Rc<x11rb::rust_connection::RustConnection>,
mut window_geometry: WindowGeometry,
monitor_geometry: gdk::Rectangle,
window: &Window,
) -> Result<()> {
let gdk_window = window.window().context("Failed to get gdk window from gtk window")?;
window_geometry.size = crate::window::window_geometry::Coords::from_pixels(window.size());
let actual_window_rect = get_window_rectangle(window_geometry, monitor_geometry);
use x11rb::connection::Connection;
use x11rb::protocol::xproto::{ConfigureWindowAux, ConnectionExt, Window as XWindow};
let gdk_origin = gdk_window.origin();
let gdk_surface = window.surface().context("Failed to get gdk surface from gtk window")?;
if actual_window_rect.x() != gdk_origin.1 || actual_window_rect.y() != gdk_origin.2 {
gdk_window.move_(actual_window_rect.x(), actual_window_rect.y());
if let Some(x11_surface) = gdk_surface.downcast_ref::<gdk4_x11::X11Surface>() {
window_geometry.size =
crate::window::window_geometry::Coords::from_pixels(window.default_size());
let actual_window_rect = get_window_rectangle(window_geometry, monitor_geometry);
let xid = x11_surface.xid();
let aux = ConfigureWindowAux::new()
.x(actual_window_rect.x() as i32)
.y(actual_window_rect.y() as i32);
conn.as_ref().configure_window(xid as XWindow, &aux)?;
conn.as_ref().flush()?;
}
Ok(())
}
fn on_screen_changed(window: &Window, _old_screen: Option<&gdk::Screen>) {
let visual = gtk::prelude::GtkWindowExt::screen(window).and_then(|screen| {
screen.rgba_visual().filter(|_| screen.is_composited()).or_else(|| screen.system_visual())
});
window.set_visual(visual.as_ref());
}
// fn on_screen_changed(window: &Window, _old_screen: Option<&gdk::Screen>) {
// let visual = window.screen().and_then(|screen| {
// screen.rgba_visual().filter(|_| screen.is_composited()).or_else(|| screen.system_visual())
// });
// window.set_visual(visual.as_ref());
// }
/// Get the monitor geometry of a given monitor, or the default if none is given
fn get_gdk_monitor(identifier: Option<MonitorIdentifier>) -> Result<Monitor> {
@@ -997,35 +1251,46 @@ fn get_gdk_monitor(identifier: Option<MonitorIdentifier>) -> Result<Monitor> {
mon.with_context(|| {
let head = format!("Failed to get monitor {}\nThe available monitors are:", ident);
let mut body = String::new();
for m in 0..display.n_monitors() {
if let Some(model) = display.monitor(m).and_then(|x| x.model()) {
body.push_str(format!("\n\t[{}] {}", m, model).as_str());
let monitors = display.monitors();
for i in 0..monitors.n_items() {
if let Some(monitor) = monitors.item(i).and_downcast::<gdk::Monitor>() {
if let Some(model) = monitor.model() {
body.push_str(format!("\n\t[{}] {}", i, model).as_str());
}
}
}
format!("{}{}", head, body)
})?
}
None => display
.primary_monitor()
.context("Failed to get primary monitor from GTK. Try explicitly specifying the monitor on your window.")?,
None => {
let monitors = display.monitors();
if monitors.n_items() == 0 {
anyhow::bail!("No monitors found on the display");
}
monitors
.item(0)
.and_downcast::<gdk::Monitor>()
.context("Failed to get the primary monitor from the list of monitors")?
}
};
Ok(monitor)
}
/// Get the name of monitor plug for given monitor number
/// workaround gdk not providing this information on wayland in regular calls
/// gdk_screen_get_monitor_plug_name is deprecated but works fine for that case
fn get_monitor_plug_name(display: &gdk::Display, monitor_num: i32) -> Option<&str> {
unsafe {
use glib::translate::ToGlibPtr;
let plug_name_pointer = gdk_sys::gdk_screen_get_monitor_plug_name(
display.default_screen().to_glib_none().0,
monitor_num,
);
use std::ffi::CStr;
CStr::from_ptr(plug_name_pointer).to_str().ok()
}
}
// /// Get the name of monitor plug for given monitor number
// /// workaround gdk not providing this information on wayland in regular calls
// /// gdk_screen_get_monitor_plug_name is deprecated but works fine for that case
// fn get_monitor_plug_name(display: &gdk::Display, monitor_num: i32) -> Option<&str> {
// unsafe {
// use glib::translate::ToGlibPtr;
// let plug_name_pointer = gdk_sys::gdk_screen_get_monitor_plug_name(
// display.default_screen().to_glib_none().0,
// monitor_num,
// );
// use std::ffi::CStr;
// CStr::from_ptr(plug_name_pointer).to_str().ok()
// }
// }
/// Returns the [Monitor][gdk::Monitor] structure corresponding to the identifer.
/// Outside of x11, only [MonitorIdentifier::Numeric] is supported
@@ -1033,6 +1298,7 @@ pub fn get_monitor_from_display(
display: &gdk::Display,
identifier: &MonitorIdentifier,
) -> Option<gdk::Monitor> {
let monitors = display.monitors();
match identifier {
MonitorIdentifier::List(list) => {
for ident in list {
@@ -1042,13 +1308,27 @@ pub fn get_monitor_from_display(
}
None
}
MonitorIdentifier::Primary => display.primary_monitor(),
MonitorIdentifier::Numeric(num) => display.monitor(*num),
MonitorIdentifier::Primary => {
if monitors.n_items() > 0 {
monitors.item(0).and_downcast::<gdk::Monitor>()
} else {
None
}
}
MonitorIdentifier::Numeric(num) => {
if *num < monitors.n_items() as i32 {
monitors.item(*num as u32).and_downcast::<gdk::Monitor>()
} else {
None
}
}
MonitorIdentifier::Name(name) => {
for m in 0..display.n_monitors() {
if let Some(model) = display.monitor(m).and_then(|x| x.model()) {
if model == *name || Some(name.as_str()) == get_monitor_plug_name(display, m) {
return display.monitor(m);
for i in 0..monitors.n_items() {
if let Some(monitor) = monitors.item(i).and_downcast::<gdk::Monitor>() {
if let Some(model) = monitor.model() {
if model == *name {
return Some(monitor);
}
}
}
}

View File

@@ -1,4 +1,4 @@
//! Module concerned with handling the global application lifecycle of eww.
//! Module concerned with handling the global application lifecycle of ewwii.
//! Currently, this only means handling application exit by providing a global
//! `recv_exit()` function which can be awaited to receive an event in case of application termination.

View File

@@ -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;
@@ -15,8 +16,11 @@ use rhai_impl::{ast::WidgetNode, parser::ParseConfig};
/// Load an [`EwwiiConfig`] from the config dir of the given [`crate::EwwiiPaths`],
/// resetting and applying the global YuckFiles object in [`crate::error_handling_ctx`].
pub fn read_from_ewwii_paths(eww_paths: &EwwiiPaths) -> Result<EwwiiConfig> {
EwwiiConfig::read_from_dir(eww_paths)
pub fn read_from_ewwii_paths(
eww_paths: &EwwiiPaths,
parser: &mut ParseConfig,
) -> Result<EwwiiConfig> {
EwwiiConfig::read_from_dir(eww_paths, parser)
}
/// Ewwii configuration structure.
@@ -24,7 +28,7 @@ pub fn read_from_ewwii_paths(eww_paths: &EwwiiPaths) -> Result<EwwiiConfig> {
pub struct EwwiiConfig {
windows: HashMap<String, WindowDefinition>,
root_node: Option<Rc<WidgetNode>>,
compiled_ast: Option<Rc<AST>>,
compiled_ast: Option<Rc<RefCell<AST>>>,
}
#[derive(Debug, Clone)]
@@ -37,15 +41,12 @@ pub struct WindowDefinition {
impl EwwiiConfig {
/// Load an [`EwwiiConfig`] from the config dir of the given [`crate::EwwiiPaths`], reading the main config file.
pub fn read_from_dir(eww_paths: &EwwiiPaths) -> Result<Self> {
pub fn read_from_dir(eww_paths: &EwwiiPaths, config_parser: &mut ParseConfig) -> Result<Self> {
let rhai_path = eww_paths.get_rhai_path();
if !rhai_path.exists() {
bail!("The configuration file `{}` does not exist", rhai_path.display());
}
// initialize configuration parser
let mut config_parser = ParseConfig::new();
// get code from file
let rhai_code = config_parser.code_from_file(&rhai_path)?;
@@ -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;
}
}

View File

@@ -1,6 +1,7 @@
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
use crate::window_initiator::WindowInitiator;
use gtk4::Window;
use gtk::gdk;
use gtk4::gdk;
#[cfg(feature = "wayland")]
pub use platform_wayland::WaylandBackend;
@@ -32,10 +33,15 @@ impl DisplayBackend for NoBackend {
fn initialize_window(
_window_init: &WindowInitiator,
_monitor: gdk::Rectangle,
x: i32,
y: i32,
_x: i32,
_y: i32,
) -> Option<Window> {
Some(Window::new(gtk::WindowType::Toplevel, x, y))
// top level
let window = Window::new();
// window.move_(x, y);
Some(window)
}
}
@@ -45,10 +51,11 @@ mod platform_wayland {
use crate::window::backend_window_options::WlWindowFocusable;
use crate::window::window_definition::WindowStacking;
use crate::window::window_geometry::AnchorAlignment;
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
use gtk::gdk;
use gtk::prelude::*;
use gtk_layer_shell::{KeyboardMode, LayerShell};
use crate::window_initiator::WindowInitiator;
use gtk4::gdk;
use gtk4::prelude::*;
use gtk4::Window;
use gtk4_layer_shell::{KeyboardMode, LayerShell};
pub struct WaylandBackend;
@@ -59,17 +66,11 @@ mod platform_wayland {
fn initialize_window(
window_init: &WindowInitiator,
monitor: gdk::Rectangle,
x: i32,
y: i32,
_x: i32,
_y: i32,
) -> Option<Window> {
let window = Window::new(gtk::WindowType::Toplevel, x, y);
// Sets the keyboard interactivity
match window_init.backend_options.wayland.focusable {
WlWindowFocusable::None => window.set_keyboard_mode(KeyboardMode::None),
WlWindowFocusable::Exclusive => window.set_keyboard_mode(KeyboardMode::Exclusive),
WlWindowFocusable::OnDemand => window.set_keyboard_mode(KeyboardMode::OnDemand),
}
let window = Window::new();
// window.move_(x, y);
window.set_resizable(window_init.resizable);
@@ -80,7 +81,7 @@ mod platform_wayland {
if let Some(ident) = window_init.monitor.clone() {
let display = gdk::Display::default().expect("could not get default display");
if let Some(monitor) = crate::app::get_monitor_from_display(&display, &ident) {
window.set_monitor(&monitor);
window.set_monitor(Some(&monitor));
} else {
return None;
}
@@ -88,16 +89,16 @@ mod platform_wayland {
// Sets the layer where the layer shell surface will spawn
match window_init.stacking {
WindowStacking::Foreground => window.set_layer(gtk_layer_shell::Layer::Top),
WindowStacking::Foreground => window.set_layer(gtk4_layer_shell::Layer::Top),
WindowStacking::Background => {
window.set_layer(gtk_layer_shell::Layer::Background)
window.set_layer(gtk4_layer_shell::Layer::Background)
}
WindowStacking::Bottom => window.set_layer(gtk_layer_shell::Layer::Bottom),
WindowStacking::Overlay => window.set_layer(gtk_layer_shell::Layer::Overlay),
WindowStacking::Bottom => window.set_layer(gtk4_layer_shell::Layer::Bottom),
WindowStacking::Overlay => window.set_layer(gtk4_layer_shell::Layer::Overlay),
}
if let Some(namespace) = &window_init.backend_options.wayland.namespace {
window.set_namespace(namespace);
window.set_namespace(Some(namespace));
}
if let Some(geometry) = window_init.geometry {
@@ -118,23 +119,23 @@ mod platform_wayland {
AnchorAlignment::END => bottom = true,
}
window.set_anchor(gtk_layer_shell::Edge::Left, left);
window.set_anchor(gtk_layer_shell::Edge::Right, right);
window.set_anchor(gtk_layer_shell::Edge::Top, top);
window.set_anchor(gtk_layer_shell::Edge::Bottom, bottom);
window.set_anchor(gtk4_layer_shell::Edge::Left, left);
window.set_anchor(gtk4_layer_shell::Edge::Right, right);
window.set_anchor(gtk4_layer_shell::Edge::Top, top);
window.set_anchor(gtk4_layer_shell::Edge::Bottom, bottom);
let xoffset = geometry.offset.x.pixels_relative_to(monitor.width());
let yoffset = geometry.offset.y.pixels_relative_to(monitor.height());
if left {
window.set_layer_shell_margin(gtk_layer_shell::Edge::Left, xoffset);
window.set_margin(gtk4_layer_shell::Edge::Left, xoffset);
} else {
window.set_layer_shell_margin(gtk_layer_shell::Edge::Right, xoffset);
window.set_margin(gtk4_layer_shell::Edge::Right, xoffset);
}
if bottom {
window.set_layer_shell_margin(gtk_layer_shell::Edge::Bottom, yoffset);
window.set_margin(gtk4_layer_shell::Edge::Bottom, yoffset);
} else {
window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, yoffset);
window.set_margin(gtk4_layer_shell::Edge::Top, yoffset);
}
// https://github.com/elkowar/eww/issues/296
if window_init.backend_options.wayland.exclusive
@@ -149,6 +150,13 @@ mod platform_wayland {
}
}
// Sets the keyboard interactivity
match window_init.backend_options.wayland.focusable {
WlWindowFocusable::None => window.set_keyboard_mode(KeyboardMode::None),
WlWindowFocusable::Exclusive => window.set_keyboard_mode(KeyboardMode::Exclusive),
WlWindowFocusable::OnDemand => window.set_keyboard_mode(KeyboardMode::OnDemand),
}
Some(window)
}
}
@@ -159,11 +167,13 @@ mod platform_x11 {
use crate::window::backend_window_options::Side;
use crate::window::backend_window_options::X11WindowType;
use crate::window::window_definition::WindowStacking;
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
use crate::window_initiator::WindowInitiator;
use anyhow::{Context, Result};
use gdk::Monitor;
use gtk::gdk;
use gtk::{self, prelude::*};
use gtk4::gdk;
use gtk4::Window;
use gtk4::{self, prelude::*};
use std::rc::Rc;
use x11rb::protocol::xproto::ConnectionExt;
use x11rb::{
@@ -183,23 +193,19 @@ mod platform_x11 {
fn initialize_window(
window_init: &WindowInitiator,
_monitor: gdk::Rectangle,
x: i32,
y: i32,
_x: i32,
_y: i32,
) -> Option<Window> {
let window_type = if window_init.backend_options.x11.wm_ignore {
gtk::WindowType::Popup
} else {
gtk::WindowType::Toplevel
};
let window = Window::new(window_type, x, y);
let window = Window::new();
if window_init.backend_options.x11.wm_ignore {
// popup
window.set_decorated(false);
window.set_modal(true);
}; // else: normal, toplevel
// window.move_(x, y);
window.set_resizable(window_init.resizable);
window.set_keep_above(window_init.stacking == WindowStacking::Foreground);
window.set_keep_below(window_init.stacking == WindowStacking::Background);
if window_init.backend_options.x11.sticky {
window.stick();
} else {
window.unstick();
}
Some(window)
}
}
@@ -215,9 +221,9 @@ mod platform_x11 {
}
struct X11BackendConnection {
conn: RustConnection<DefaultStream>,
conn: Rc<RustConnection<DefaultStream>>,
root_window: u32,
atoms: AtomCollection,
atoms: Rc<AtomCollection>,
}
impl X11BackendConnection {
@@ -225,7 +231,11 @@ mod platform_x11 {
let (conn, screen_num) = RustConnection::connect(None)?;
let screen = conn.setup().roots[screen_num].clone();
let atoms = AtomCollection::new(&conn)?.reply()?;
Ok(X11BackendConnection { conn, root_window: screen.root, atoms })
Ok(X11BackendConnection {
conn: Rc::new(conn),
root_window: screen.root,
atoms: Rc::new(atoms),
})
}
fn set_xprops_for(
@@ -236,11 +246,13 @@ mod platform_x11 {
) -> Result<()> {
let monitor_rect = monitor.geometry();
let scale_factor = monitor.scale_factor() as u32;
let gdk_window = window.window().context("Couldn't get gdk window from gtk window")?;
let win_id = gdk_window
.downcast_ref::<gdkx11::X11Window>()
let gdk_surface =
window.surface().context("Couldn't get gdk window from gtk window")?;
let win_id = gdk_surface
.downcast_ref::<gdk4_x11::X11Surface>()
.context("Failed to get x11 window for gtk window")?
.xid() as u32;
let strut_def = window_init.backend_options.x11.struts;
let root_window_geometry = self.conn.get_geometry(self.root_window)?.reply()?;
@@ -313,8 +325,63 @@ mod platform_x11 {
)?
.check()?;
// apply the stickiness and fg/bg thingy
let sticky_clone = window_init.backend_options.x11.sticky.clone();
let stacking_clone = window_init.stacking.clone();
let root_window = self.root_window;
let conn = Rc::clone(&self.conn);
let atoms = Rc::clone(&self.atoms);
window.connect_show(move |_| {
if let Err(err) = Self::set_window_states(
&*conn,
win_id,
&*atoms,
sticky_clone,
stacking_clone,
root_window,
) {
log::error!("Failed to set window state: {}", err);
}
});
self.conn.flush().context("Failed to send requests to X server")
}
fn set_window_states(
conn: &impl Connection,
win: u32,
atoms: &AtomCollection,
sticky: bool,
stacking: WindowStacking,
root_window: u32,
) -> Result<()> {
let mut states = Vec::new();
if sticky {
states.push(atoms._NET_WM_STATE_STICKY);
}
match stacking {
WindowStacking::Foreground => states.push(atoms._NET_WM_STATE_ABOVE),
WindowStacking::Background => states.push(atoms._NET_WM_STATE_BELOW),
_ => {}
}
for state in states {
let event = ClientMessageEvent {
response_type: CLIENT_MESSAGE_EVENT,
format: 32,
sequence: 0,
window: win,
type_: atoms._NET_WM_STATE,
data: ClientMessageData::from([1, state, 0, 0, 0]),
};
conn.send_event(true, root_window, EventMask::PROPERTY_CHANGE, event)?;
}
conn.flush()?;
Ok(())
}
}
x11rb::atom_manager! {

View File

@@ -18,9 +18,9 @@
#![allow(rustdoc::private_intra_doc_links)]
// getting gtk stuff
extern crate gtk;
extern crate gtk4;
#[cfg(feature = "wayland")]
extern crate gtk_layer_shell as gtk_layer_shell;
extern crate gtk4_layer_shell as gtk4_layer_shell;
// imporing dependencies
use anyhow::{Context, Result};
@@ -48,6 +48,7 @@ mod gen_diagnostic_macro;
mod ipc_server;
mod opts;
mod paths;
mod plugin;
mod server;
mod util;
mod widgets;
@@ -147,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,
};
@@ -167,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());
@@ -181,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
}
@@ -222,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);

View File

@@ -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)
@@ -217,6 +231,67 @@ pub enum ActionWithServer {
#[arg(long = "sprint", short = 'p')]
print: bool,
},
/// Set a plugin (.so) to the ewwii binary
#[command(name = "set-plugin")]
SetPlugin {
/// The .so file to load
#[arg(value_parser = absolute_file_path_parser)]
file_path: String,
},
}
/// Subcommands for widget control
#[derive(Subcommand, Debug, Serialize, Deserialize, PartialEq)]
pub enum WidgetControlAction {
/// Remove widget by name
Remove {
/// Names of the widgets to remove
names: Vec<String>,
},
/// Create widgets
Create {
/// Rhai code to create widgets from
rhai_codes: Vec<String>,
/// Name of the widget to add these widgets as a child to
#[arg(long = "parent", short = 'p')]
parent_name: String,
},
/// Update properties of a widget by name
PropertyUpdate {
/// Properties and its value
///
/// Format: value="val1" widget_name="val2"
#[arg(value_parser = parse_inject_var_map)]
property_and_value: HashMap<String, String>,
/// Name of the widget to update the property of
#[arg(long = "widget", short = 'w')]
widget_name: String,
},
/// Add a class to a widget with given name
AddClass {
/// The class to add to the widget
class: String,
/// Name of the widget to add class to
#[arg(long = "widget", short = 'w')]
widget_name: String,
},
/// Remove a class to a widget with given name
RemoveClass {
/// The class to remove from the widget
class: String,
/// Name of the widget to remove class from
#[arg(long = "widget", short = 'w')]
widget_name: String,
},
}
impl Opt {
@@ -278,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,
})
}
@@ -355,6 +437,12 @@ impl ActionWithServer {
sender,
})
}
ActionWithServer::SetPlugin { file_path } => {
return with_response_channel(|sender| app::DaemonCommand::SetPlugin {
file_path,
sender,
})
}
};
(command, None)
}
@@ -431,3 +519,10 @@ fn parse_inject_var_map(s: &str) -> Result<HashMap<String, String>, String> {
}
Ok(map)
}
fn absolute_file_path_parser(s: &str) -> Result<String, String> {
let p = std::path::Path::new(s);
std::fs::canonicalize(p)
.map_err(|e| format!("Failed to canonicalize '{}': {}", s, e))
.map(|abs_path| abs_path.to_string_lossy().into_owned())
}

View File

@@ -0,0 +1,86 @@
use ewwii_plugin_api::{rhai_backend, widget_backend, EwwiiAPI};
use rhai::{Array, Dynamic, Engine, EvalAltResult};
use std::sync::mpsc::{channel as mpsc_channel, Receiver, Sender};
pub(crate) struct EwwiiImpl {
pub(crate) requestor: Sender<PluginRequest>,
}
impl EwwiiAPI for EwwiiImpl {
// General
// "PCL = Plugin Controlled Log"
fn print(&self, msg: &str) {
println!("[PCL] {}", msg);
}
fn log(&self, msg: &str) {
log::info!("[PCL] {}", msg);
}
fn warn(&self, msg: &str) {
log::warn!("[PCL] {}", msg);
}
fn error(&self, msg: &str) {
log::error!("[PCL] {}", msg);
}
// Rhai Manipulation Stuff
fn rhai_engine_action(&self, f: Box<dyn FnOnce(&mut Engine) + Send>) -> Result<(), String> {
self.requestor
.send(PluginRequest::RhaiEngineAct(f))
.map_err(|_| "Failed to send request to host".to_string())?;
Ok(())
}
fn register_function(
&self,
name: String,
namespace: rhai_backend::RhaiFnNamespace,
f: Box<dyn Fn(Array) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync>,
) -> Result<(), String> {
let func_info = (name, namespace, f);
self.requestor
.send(PluginRequest::RegisterFunc(func_info))
.map_err(|_| "Failed to send request to host".to_string())?;
Ok(())
}
// Widget Rendering & Logic
fn list_widget_ids(&self) -> Result<Vec<u64>, String> {
let (tx, rx): (Sender<Vec<u64>>, Receiver<Vec<u64>>) = mpsc_channel();
self.requestor
.send(PluginRequest::ListWidgetIds(tx))
.map_err(|_| "Failed to send request to host".to_string())?;
match rx.recv() {
Ok(r) => Ok(r),
Err(e) => Err(e.to_string()),
}
}
fn widget_reg_action(
&self,
f: Box<dyn FnOnce(&mut widget_backend::WidgetRegistryRepr) + Send>,
) -> Result<(), String> {
self.requestor
.send(PluginRequest::WidgetRegistryAct(f))
.map_err(|_| "Failed to send request to host".to_string())?;
Ok(())
}
}
pub(crate) enum PluginRequest {
RhaiEngineAct(Box<dyn FnOnce(&mut Engine) + Send>),
RegisterFunc(
(
String,
rhai_backend::RhaiFnNamespace,
Box<dyn Fn(Array) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync>,
),
),
ListWidgetIds(Sender<Vec<u64>>),
WidgetRegistryAct(Box<dyn FnOnce(&mut widget_backend::WidgetRegistryRepr) + Send>),
}

View File

@@ -5,16 +5,16 @@ use crate::{
error_handling_ctx, ipc_server, EwwiiPaths,
};
use anyhow::{Context, Result};
use gtk4::prelude::{DisplayExt, ListModelExt};
use std::{
// cell::RefCell,
cell::RefCell,
collections::{HashMap, HashSet},
io::Write,
marker::PhantomData,
os::unix::io::AsRawFd,
path::Path,
// rc::Rc,
sync::{atomic::Ordering, Arc},
rc::Rc,
sync::{atomic::Ordering, Arc, RwLock},
};
use tokio::sync::mpsc::*;
@@ -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();
@@ -31,18 +32,11 @@ pub fn initialize_server<B: DisplayBackend>(
log::info!("Loading paths: {}", &paths);
let read_config = config::read_from_ewwii_paths(&paths);
let pl_handler_store: rhai_impl::updates::ReactiveVarStore =
Arc::new(RwLock::new(HashMap::new()));
let ewwii_config = match read_config {
Ok(config) => config,
Err(err) => {
error_handling_ctx::print_error(err);
config::EwwiiConfig::default()
// TODO: Maybe do something so that we can exit if user wants.
// std::process::exit(1);
}
};
let config_parser =
Rc::new(RefCell::new(rhai_impl::parser::ParseConfig::new(Some(pl_handler_store.clone()))));
cleanup_log_dir(paths.get_log_dir())?;
@@ -76,29 +70,57 @@ pub fn initialize_server<B: DisplayBackend>(
if B::IS_WAYLAND {
std::env::set_var("GDK_BACKEND", "wayland")
}
gtk::init()?;
gtk4::init()?;
let main_loop = gtk4::glib::MainLoop::new(None, false);
let mut app: App<B> = app::App {
ewwii_config,
ewwii_config: config::EwwiiConfig::default(),
open_windows: HashMap::new(),
failed_windows: HashSet::new(),
instance_id_to_args: HashMap::new(),
css_provider: gtk::CssProvider::new(),
css_provider: gtk4::CssProvider::new(),
reloading: false,
app_evt_send: ui_send.clone(),
window_close_timer_abort_senders: HashMap::new(),
widget_reg_store: std::rc::Rc::new(std::sync::Mutex::new(None)),
pl_handler_store: None,
pl_handler_store,
clear_pl_onclose: HashMap::new(),
rt_engine_config: EngineConfValues::default(),
config_parser,
paths,
gtk_main_loop: main_loop.clone(),
phantom: PhantomData,
};
if let Some(screen) = gtk::gdk::Screen::default() {
gtk::StyleContext::add_provider_for_screen(
&screen,
&app.css_provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
// start up plugins
if let Some(ewwii_plugin) = ewwii_plugin_path {
if let Err(e) = app.set_ewwii_plugin(ewwii_plugin) {
error_handling_ctx::print_error(e);
}
}
let mut config_parser_mut = app.config_parser.borrow_mut();
let read_config = config::read_from_ewwii_paths(&app.paths, &mut *config_parser_mut);
// free the temporary parser borrow
drop(config_parser_mut);
match read_config {
Ok(new_config) => {
app.ewwii_config = new_config;
}
Err(err) => {
error_handling_ctx::print_error(err);
// TODO: Maybe do something so that we can exit if user wants.
// std::process::exit(1);
}
};
if let Some(display) = gtk4::gdk::Display::default() {
gtk4::style_context_add_provider_for_display(&display, &app.css_provider, 900);
}
if let Ok((file_id, css)) = config::scss::parse_scss_from_config(app.paths.get_config_dir()) {
@@ -112,7 +134,7 @@ pub fn initialize_server<B: DisplayBackend>(
// initialize all the handlers and tasks running asyncronously
let tokio_handle = init_async_part(app.paths.clone(), ui_send);
gtk::glib::MainContext::default().spawn_local(async move {
gtk4::glib::MainContext::default().spawn_local(async move {
// if an action was given to the daemon initially, execute it first.
if let Some(action) = action {
app.handle_command(action).await;
@@ -134,20 +156,26 @@ pub fn initialize_server<B: DisplayBackend>(
// allow the GTK main thread to do tokio things
let _g = tokio_handle.enter();
gtk::main();
main_loop.run();
log::info!("main application thread finished");
Ok(ForkResult::Child)
}
fn connect_monitor_added(ui_send: UnboundedSender<DaemonCommand>) {
let display = gtk::gdk::Display::default().expect("could not get default display");
display.connect_monitor_added({
move |_display: &gtk::gdk::Display, _monitor: &gtk::gdk::Monitor| {
log::info!("New monitor connected, reloading configuration");
let _ = reload_config_and_css(&ui_send);
}
});
if let Some(display) = gtk4::gdk::Display::default() {
let monitors = display.monitors();
monitors.connect_items_changed(gtk4::glib::clone!(
#[strong]
ui_send,
move |_, _, _, _| {
let _ = reload_config_and_css(&ui_send);
}
));
} else {
log::warn!("Cannot access GDK Display on this session (likely Wayland)");
}
}
fn reload_config_and_css(ui_send: &UnboundedSender<DaemonCommand>) -> Result<()> {

View File

@@ -1,7 +1,8 @@
use anyhow::Result;
use gtk::gdk::prelude::Cast;
use gtk4::gdk::prelude::Cast;
use crate::{config::WindowDefinition, widgets::widget_definitions::*};
use crate::config::WindowDefinition;
use crate::widgets::widget_definitions::*;
use rhai_impl::ast::WidgetNode;
@@ -17,7 +18,7 @@ pub enum WidgetInput<'a> {
pub fn build_gtk_widget<'a>(
input: &'a WidgetInput<'a>,
widget_reg: &mut WidgetRegistry,
) -> Result<gtk::Widget> {
) -> Result<gtk4::Widget> {
let node: &'a WidgetNode = match input {
WidgetInput::Node(n) => n,
WidgetInput::BorrowedNode(n) => n,
@@ -30,7 +31,7 @@ pub fn build_gtk_widget<'a>(
fn build_gtk_widget_from_node(
root_node: &WidgetNode,
widget_reg: &mut WidgetRegistry,
) -> Result<gtk::Widget> {
) -> Result<gtk4::Widget> {
/*
When a a new widget is added to the build process,
make sure to update get_id_to_props_map() found in
@@ -40,23 +41,30 @@ fn build_gtk_widget_from_node(
let gtk_widget = match root_node {
WidgetNode::Box { props, children } => build_gtk_box(props, children, widget_reg)?.upcast(),
WidgetNode::CenterBox { props, children } => {
build_center_box(props, children, widget_reg)?.upcast()
WidgetNode::FlowBox { props, children } => {
build_gtk_flowbox(props, children, widget_reg)?.upcast()
}
WidgetNode::EventBox { props, children } => {
build_gtk_event_box(props, children, widget_reg)?.upcast()
build_event_box(props, children, widget_reg)?.upcast()
}
WidgetNode::ToolTip { props, children } => {
build_tooltip(props, children, widget_reg)?.upcast()
}
WidgetNode::LocalBind { props, children } => {
build_localbind_util(props, children, widget_reg)?.upcast()
}
WidgetNode::WidgetAction { props, children } => {
build_widgetaction_util(props, children, widget_reg)?.upcast()
}
WidgetNode::CircularProgress { props } => {
build_circular_progress_bar(props, widget_reg)?.upcast()
}
WidgetNode::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::Transform { props } => build_transform(props, widget_reg)?.upcast(),
WidgetNode::Scale { props } => build_gtk_scale(props, widget_reg)?.upcast(),
WidgetNode::Progress { props } => build_gtk_progress(props, widget_reg)?.upcast(),
WidgetNode::Image { props } => build_gtk_image(props, widget_reg)?.upcast(),
WidgetNode::Image { props } => build_image(props, widget_reg)?.upcast(),
WidgetNode::Button { props } => build_gtk_button(props, widget_reg)?.upcast(),
WidgetNode::Label { props } => build_gtk_label(props, widget_reg)?.upcast(),
// WIDGET_NAME_LITERAL => build_gtk_literal(node)?.upcast(),

View File

@@ -1,206 +1,124 @@
use anyhow::{anyhow, Result};
use gtk::glib::{self, object_subclass, prelude::*, wrapper, Properties};
use gtk::{cairo, gdk, prelude::*, subclass::prelude::*};
use std::cell::RefCell;
use glib::Object;
use gtk4::glib;
use gtk4::prelude::*;
use gtk4::subclass::prelude::*;
use gtk4::{cairo, gdk, graphene};
use std::cell::Cell;
use crate::error_handling_ctx;
mod imp {
use super::*;
wrapper! {
pub struct CircProg(ObjectSubclass<CircProgPriv>)
@extends gtk::Bin, gtk::Container, gtk::Widget;
}
#[derive(Properties)]
#[properties(wrapper_type = CircProg)]
pub struct CircProgPriv {
#[property(
get,
set,
nick = "Starting at",
blurb = "Starting at",
minimum = 0f64,
maximum = 100f64,
default = 0f64
)]
start_at: RefCell<f64>,
#[property(
get,
set,
nick = "Value",
blurb = "The value",
minimum = 0f64,
maximum = 100f64,
default = 0f64
)]
value: RefCell<f64>,
#[property(
get,
set,
nick = "Thickness",
blurb = "Thickness",
minimum = 0f64,
maximum = 100f64,
default = 1f64
)]
thickness: RefCell<f64>,
#[property(get, set, nick = "Clockwise", blurb = "Clockwise", default = true)]
clockwise: RefCell<bool>,
content: RefCell<Option<gtk::Widget>>,
}
// This should match the default values from the ParamSpecs
impl Default for CircProgPriv {
fn default() -> Self {
CircProgPriv {
start_at: RefCell::new(0.0),
value: RefCell::new(0.0),
thickness: RefCell::new(1.0),
clockwise: RefCell::new(true),
content: RefCell::new(None),
}
}
}
impl ObjectImpl for CircProgPriv {
fn properties() -> &'static [glib::ParamSpec] {
Self::derived_properties()
pub struct CircProg {
pub value: Cell<f64>,
pub start_at: Cell<f64>,
pub thickness: Cell<f64>,
pub clockwise: Cell<bool>,
pub fg_color: Cell<gdk::RGBA>,
pub bg_color: Cell<gdk::RGBA>,
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"value" => {
self.value.replace(value.get().unwrap());
self.obj().queue_draw(); // Queue a draw call with the updated value
impl Default for CircProg {
fn default() -> Self {
Self {
value: Cell::new(0.0),
start_at: Cell::new(0.0),
thickness: Cell::new(8.0),
clockwise: Cell::new(true),
fg_color: Cell::new(gdk::RGBA::new(1.0, 0.0, 0.0, 1.0)),
bg_color: Cell::new(gdk::RGBA::new(0.0, 0.0, 0.0, 0.1)),
}
"thickness" => {
self.thickness.replace(value.get().unwrap());
}
}
#[glib::object_subclass]
impl ObjectSubclass for CircProg {
const NAME: &'static str = "CircProg";
type Type = super::CircProg;
type ParentType = gtk4::Widget;
}
impl ObjectImpl for CircProg {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.add_css_class("circular-progress");
}
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecDouble::builder("value")
.minimum(0.0)
.maximum(100.0)
.default_value(0.0)
.build(),
glib::ParamSpecDouble::builder("start-at")
.minimum(0.0)
.maximum(100.0)
.default_value(0.0)
.build(),
glib::ParamSpecDouble::builder("thickness")
.minimum(1.0)
.maximum(50.0)
.default_value(8.0)
.build(),
glib::ParamSpecBoolean::builder("clockwise").default_value(true).build(),
glib::ParamSpecBoxed::builder::<gdk::RGBA>("fg-color").build(),
glib::ParamSpecBoxed::builder::<gdk::RGBA>("bg-color").build(),
]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"value" => self.value.set(value.get().unwrap()),
"start-at" => self.start_at.set(value.get().unwrap()),
"thickness" => self.thickness.set(value.get().unwrap()),
"clockwise" => self.clockwise.set(value.get().unwrap()),
"fg-color" => self.fg_color.set(value.get().unwrap()),
"bg-color" => self.bg_color.set(value.get().unwrap()),
x => panic!("Tried to set inexistant property of CircProg: {}", x,),
}
"start-at" => {
self.start_at.replace(value.get().unwrap());
self.obj().queue_draw();
}
fn property(&self, _: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"value" => self.value.get().to_value(),
"start-at" => self.start_at.get().to_value(),
"thickness" => self.thickness.get().to_value(),
"clockwise" => self.clockwise.get().to_value(),
"fg-color" => self.fg_color.get().to_value(),
"bg-color" => self.bg_color.get().to_value(),
x => panic!("Tried to get inexistant property of CircProg: {}", x,),
}
"clockwise" => {
self.clockwise.replace(value.get().unwrap());
}
x => panic!("Tried to set inexistant property of CircProg: {}", x,),
}
}
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
self.derived_property(id, pspec)
}
}
#[object_subclass]
impl ObjectSubclass for CircProgPriv {
type ParentType = gtk::Bin;
type Type = CircProg;
const NAME: &'static str = "CircProg";
fn class_init(klass: &mut Self::Class) {
klass.set_css_name("circular-progress");
}
}
impl Default for CircProg {
fn default() -> Self {
Self::new()
}
}
impl CircProg {
pub fn new() -> Self {
glib::Object::new::<Self>()
}
}
impl ContainerImpl for CircProgPriv {
fn add(&self, widget: &gtk::Widget) {
if let Some(content) = &*self.content.borrow() {
// TODO: Handle this error when populating children widgets instead
error_handling_ctx::print_error(anyhow!(
"Error, trying to add multiple children to a circular-progress widget"
));
self.parent_remove(content);
impl WidgetImpl for CircProg {
fn measure(&self, _orientation: gtk4::Orientation, _for_size: i32) -> (i32, i32, i32, i32) {
let min_size = 32;
let natural_size = 64;
(min_size, natural_size, -1, -1)
}
self.parent_add(widget);
self.content.replace(Some(widget.clone()));
}
}
fn calc_widget_lowest_preferred_dimension(widget: &gtk::Widget) -> (i32, i32) {
let preferred_width = widget.preferred_width();
let preferred_height = widget.preferred_height();
let min_lowest = i32::min(preferred_width.0, preferred_height.0);
let natural_lowest = i32::min(preferred_width.1, preferred_height.1);
(min_lowest, natural_lowest)
}
fn snapshot(&self, snapshot: &gtk4::Snapshot) {
let value = self.value.get();
let start_at = self.start_at.get();
let thickness = self.thickness.get();
let clockwise = self.clockwise.get();
let fg_color = self.fg_color.get();
let bg_color = self.bg_color.get();
impl BinImpl for CircProgPriv {}
impl WidgetImpl for CircProgPriv {
// We overwrite preferred_* so that overflowing content from the children gets cropped
// We return min(child_width, child_height)
fn preferred_width(&self) -> (i32, i32) {
let styles = self.obj().style_context();
let margin = styles.margin(gtk::StateFlags::NORMAL);
if let Some(child) = &*self.content.borrow() {
let (min_child, natural_child) = calc_widget_lowest_preferred_dimension(child);
(
min_child + margin.right as i32 + margin.left as i32,
natural_child + margin.right as i32 + margin.left as i32,
)
} else {
let empty_width =
(2 * *self.thickness.borrow() as i32) + margin.right as i32 + margin.left as i32;
(empty_width, empty_width)
}
}
fn preferred_width_for_height(&self, _height: i32) -> (i32, i32) {
self.preferred_width()
}
fn preferred_height(&self) -> (i32, i32) {
let styles = self.obj().style_context();
let margin = styles.margin(gtk::StateFlags::NORMAL);
if let Some(child) = &*self.content.borrow() {
let (min_child, natural_child) = calc_widget_lowest_preferred_dimension(child);
(
min_child + margin.bottom as i32 + margin.top as i32,
natural_child + margin.bottom as i32 + margin.top as i32,
)
} else {
let empty_height =
(2 * *self.thickness.borrow() as i32) + margin.right as i32 + margin.left as i32;
(empty_height, empty_height)
}
}
fn preferred_height_for_width(&self, _width: i32) -> (i32, i32) {
self.preferred_height()
}
fn draw(&self, cr: &cairo::Context) -> glib::Propagation {
let res: Result<()> = (|| {
let value = *self.value.borrow();
let start_at = *self.start_at.borrow();
let thickness = *self.thickness.borrow();
let clockwise = *self.clockwise.borrow();
let styles = self.obj().style_context();
let margin = styles.margin(gtk::StateFlags::NORMAL);
let margin_start = self.obj().margin_start() as f64;
let margin_end = self.obj().margin_end() as f64;
let margin_top = self.obj().margin_top() as f64;
let margin_bottom = self.obj().margin_bottom() as f64;
// Padding is not supported yet
let fg_color: gdk::RGBA = styles.color(gtk::StateFlags::NORMAL);
let bg_color: gdk::RGBA = styles
.style_property_for_state("background-color", gtk::StateFlags::NORMAL)
.get()?;
let (start_angle, end_angle) = if clockwise {
(0.0, perc_to_rad(value))
} else {
@@ -211,12 +129,20 @@ impl WidgetImpl for CircProgPriv {
let total_height = self.obj().allocated_height() as f64;
let center = (total_width / 2.0, total_height / 2.0);
let circle_width = total_width - margin.left as f64 - margin.right as f64;
let circle_height = total_height - margin.top as f64 - margin.bottom as f64;
let circle_width = total_width - margin_start - margin_end;
let circle_height = total_height - margin_top - margin_bottom;
let outer_ring = f64::min(circle_width, circle_height) / 2.0;
let inner_ring = (f64::min(circle_width, circle_height) / 2.0) - thickness;
cr.save()?;
// Snapshot Cairo node
let cr = snapshot.append_cairo(&graphene::Rect::new(
0.0_f32,
0.0_f32,
total_width as f32,
total_height as f32,
));
cr.save().unwrap();
// Centering
cr.translate(center.0, center.1);
@@ -226,45 +152,45 @@ impl WidgetImpl for CircProgPriv {
// Background Ring
cr.move_to(center.0, center.1);
cr.arc(center.0, center.1, outer_ring, 0.0, perc_to_rad(100.0));
cr.set_source_rgba(bg_color.red(), bg_color.green(), bg_color.blue(), bg_color.alpha());
cr.set_source_rgba(
bg_color.red().into(),
bg_color.green().into(),
bg_color.blue().into(),
bg_color.alpha().into(),
);
cr.move_to(center.0, center.1);
cr.arc(center.0, center.1, inner_ring, 0.0, perc_to_rad(100.0));
cr.set_fill_rule(cairo::FillRule::EvenOdd); // Substract one circle from the other
cr.fill()?;
cr.fill().unwrap();
// Foreground Ring
cr.move_to(center.0, center.1);
cr.arc(center.0, center.1, outer_ring, start_angle, end_angle);
cr.set_source_rgba(fg_color.red(), fg_color.green(), fg_color.blue(), fg_color.alpha());
cr.set_source_rgba(
fg_color.red().into(),
fg_color.green().into(),
fg_color.blue().into(),
fg_color.alpha().into(),
);
cr.move_to(center.0, center.1);
cr.arc(center.0, center.1, inner_ring, start_angle, end_angle);
cr.set_fill_rule(cairo::FillRule::EvenOdd); // Substract one circle from the other
cr.fill()?;
cr.restore()?;
cr.fill().unwrap();
// Draw the children widget, clipping it to the inside
if let Some(child) = &*self.content.borrow() {
cr.save()?;
cr.restore().unwrap();
}
}
}
// Center circular clip
cr.arc(center.0, center.1, inner_ring + 1.0, 0.0, perc_to_rad(100.0));
cr.set_source_rgba(bg_color.red(), 0.0, 0.0, bg_color.alpha());
cr.clip();
glib::wrapper! {
pub struct CircProg(ObjectSubclass<imp::CircProg>)
@extends gtk4::Widget,
@implements gtk4::Accessible, gtk4::Actionable, gtk4::Buildable, gtk4::ConstraintTarget;
}
// Children widget
self.obj().propagate_draw(child, cr);
cr.reset_clip();
cr.restore()?;
}
Ok(())
})();
if let Err(error) = res {
error_handling_ctx::print_error(error)
};
glib::Propagation::Proceed
impl CircProg {
pub fn new() -> Self {
Object::builder().build()
}
}

View File

@@ -1,349 +1,654 @@
use std::{cell::RefCell, collections::VecDeque};
// https://www.figuiere.net/technotes/notes/tn002/
// https://github.com/gtk-rs/examples/blob/master/src/bin/listbox_model.rs
use anyhow::{anyhow, Result};
use gtk::glib::{self, object_subclass, wrapper, Properties};
use gtk::{cairo, gdk, prelude::*, subclass::prelude::*};
//! Graph widget with time-series visualization
//!
//! The graph renders data points in a 2D canvas with time on the horizontal axis
//! and values on the vertical axis by default (non-vertical mode).
//!
//! Canvas Layout (default horizontal orientation, no flipping):
//!
//! value ↑
//! │
//! max ┼──────────────┐ (past, max)
//! │ │
//! │ │
//! │ Graph │
//! │ Area │
//! │ │
//! min ┼──────────────┘ (past, min)
//! └──────────────┴───→ time
//! (past) (now)
//!
//! Key coordinates in widget space (after margins):
//! - (0, 0) : Top-left corner (past time, max value)
//! - (width, 0) : Top-right corner (current time, max value)
//! - (0, height): Bottom-left corner (past time, min value)
//! - (width, height): Bottom-right corner (current time, min value)
//!
//! Time flows from left (past) to right (present).
//! Most recent data points appear at the right edge.
//! Older points scroll leftward as time progresses.
//!
//! The `time-range` property controls how many milliseconds of history are visible.
//! Points older than `now - time-range` are automatically pruned.
//!
//! The graph supports multiple render types (Line, Fill, Step variants)
//! and can be flipped/rotated via properties.
use crate::error_handling_ctx;
use gtk4::glib::property::PropertySet;
use gtk4::prelude::*;
use gtk4::subclass::prelude::*;
use gtk4::{gdk, glib, graphene, gsk};
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::time::{Duration, Instant};
// This widget shouldn't be a Bin/Container but I've not been
// able to subclass just a gtk::Widget
wrapper! {
pub struct Graph(ObjectSubclass<GraphPriv>)
@extends gtk::Bin, gtk::Container, gtk::Widget;
}
mod imp {
use super::*;
#[derive(Properties)]
#[properties(wrapper_type = Graph)]
pub struct GraphPriv {
#[property(get, set, nick = "Value", blurb = "The value", minimum = 0f64, maximum = f64::MAX, default = 0f64)]
value: RefCell<f64>,
const DEFAULT_VALUE: f64 = 0.0;
const DEFAULT_THICKNESS: f64 = 1.0;
const DEFAULT_MIN: f64 = 0.0;
const DEFAULT_MAX: f64 = 1.0;
const DEFAULT_DYNAMIC: bool = true;
const DEFAULT_TIME_RANGE: u32 = 10_000; // ms
const DEFAULT_FLIP_X: bool = false;
const DEFAULT_FLIP_Y: bool = false;
const DEFAULT_VERTICAL: bool = false;
const DEFAULT_ANIMATE: bool = true;
#[property(get, set, nick = "Thickness", blurb = "The Thickness", minimum = 0f64, maximum = f64::MAX, default = 1f64)]
thickness: RefCell<f64>,
pub struct Graph {
pub value: Cell<f64>,
pub thickness: Cell<f64>,
pub line_style: Cell<LineStyle>,
pub min: Cell<f64>,
pub max: Cell<f64>,
pub dynamic: Cell<bool>,
pub time_range: Cell<u32>,
pub flip_x: Cell<bool>,
pub flip_y: Cell<bool>,
pub vertical: Cell<bool>,
pub render_type: Cell<RenderType>,
#[property(get, set, nick = "Line Style", blurb = "The Line Style", default = "miter")]
line_style: RefCell<String>,
// Runtime state
history: RefCell<VecDeque<(Instant, f64)>>,
last_updated_at: RefCell<Instant>,
tick_id: RefCell<Option<gtk4::TickCallbackId>>,
min_value_cached: Cell<Option<f64>>,
max_value_cached: Cell<Option<f64>>,
has_received_value: Cell<bool>,
#[property(get, set, nick = "Maximum Value", blurb = "The Maximum Value", minimum = 0f64, maximum = f64::MAX, default = 100f64)]
min: RefCell<f64>,
// Cached path (Geometry)
cached_path: RefCell<Option<gsk::Path>>,
// The "anchor" time used to build the path (fixed time reference)
path_anchor_time: Cell<Instant>,
// Size when the path was built (for invalidation)
cached_path_size: Cell<(f32, f32)>,
}
#[property(get, set, nick = "Minumum Value", blurb = "The Minimum Value", minimum = 0f64, maximum = f64::MAX, default = 0f64)]
max: RefCell<f64>,
impl Default for Graph {
fn default() -> Self {
Self {
value: Cell::new(DEFAULT_VALUE),
thickness: Cell::new(DEFAULT_THICKNESS),
line_style: Cell::new(LineStyle::default()),
min: Cell::new(DEFAULT_MIN),
max: Cell::new(DEFAULT_MAX),
dynamic: Cell::new(DEFAULT_DYNAMIC),
time_range: Cell::new(DEFAULT_TIME_RANGE),
flip_x: Cell::new(DEFAULT_FLIP_X),
flip_y: Cell::new(DEFAULT_FLIP_Y),
vertical: Cell::new(DEFAULT_VERTICAL),
render_type: Cell::new(RenderType::default()),
#[property(get, set, nick = "Dynamic", blurb = "If it is dynamic", default = true)]
dynamic: RefCell<bool>,
history: RefCell::new(VecDeque::new()),
last_updated_at: RefCell::new(Instant::now()),
tick_id: RefCell::new(None),
min_value_cached: Cell::new(None),
max_value_cached: Cell::new(None),
has_received_value: Cell::new(false),
#[property(get, set, nick = "Time Range", blurb = "The Time Range", minimum = 0u64, maximum = u64::MAX, default = 10u64)]
time_range: RefCell<u64>,
#[property(get, set, nick = "Flip X", blurb = "Flip the x axis", default = true)]
flip_x: RefCell<bool>,
#[property(get, set, nick = "Flip Y", blurb = "Flip the y axis", default = true)]
flip_y: RefCell<bool>,
#[property(get, set, nick = "Vertical", blurb = "Exchange the x and y axes", default = false)]
vertical: RefCell<bool>,
history: RefCell<VecDeque<(std::time::Instant, f64)>>,
extra_point: RefCell<Option<(std::time::Instant, f64)>>,
last_updated_at: RefCell<std::time::Instant>,
}
impl Default for GraphPriv {
fn default() -> Self {
Self {
value: RefCell::new(0.0),
thickness: RefCell::new(1.0),
line_style: RefCell::new("miter".to_string()),
min: RefCell::new(0.0),
max: RefCell::new(100.0),
dynamic: RefCell::new(true),
time_range: RefCell::new(10),
flip_x: RefCell::new(true),
flip_y: RefCell::new(true),
vertical: RefCell::new(false),
history: RefCell::new(VecDeque::new()),
extra_point: RefCell::new(None),
last_updated_at: RefCell::new(std::time::Instant::now()),
cached_path: RefCell::new(None),
path_anchor_time: Cell::new(Instant::now()),
cached_path_size: Cell::new((0.0, 0.0)),
}
}
}
}
impl GraphPriv {
// Updates the history, removing points ouside the range
fn update_history(&self, v: (std::time::Instant, f64)) {
let mut history = self.history.borrow_mut();
let mut last_value = self.extra_point.borrow_mut();
let mut last_updated_at = self.last_updated_at.borrow_mut();
*last_updated_at = std::time::Instant::now();
#[glib::object_subclass]
impl ObjectSubclass for Graph {
const NAME: &'static str = "EwwiiGraph";
type Type = super::Graph;
type ParentType = gtk4::Widget;
}
while let Some(entry) = history.front() {
if last_updated_at.duration_since(entry.0).as_millis() as u64
> *self.time_range.borrow()
{
*last_value = history.pop_front();
impl ObjectImpl for Graph {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.add_css_class("graph");
if DEFAULT_ANIMATE {
self.set_animate(&obj, true);
}
}
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(f64::MAX)
.default_value(DEFAULT_VALUE)
.build(),
glib::ParamSpecDouble::builder("thickness")
.minimum(0.0)
.maximum(f64::MAX)
.default_value(DEFAULT_THICKNESS)
.build(),
glib::ParamSpecEnum::builder::<LineStyle>("line-style")
.default_value(LineStyle::default())
.build(),
glib::ParamSpecDouble::builder("min")
.minimum(f64::MIN)
.maximum(f64::MAX)
.default_value(DEFAULT_MIN)
.build(),
glib::ParamSpecDouble::builder("max")
.minimum(f64::MIN)
.maximum(f64::MAX)
.default_value(DEFAULT_MAX)
.build(),
glib::ParamSpecBoolean::builder("dynamic")
.default_value(DEFAULT_DYNAMIC)
.build(),
glib::ParamSpecUInt::builder("time-range")
.minimum(0)
.maximum(u32::MAX)
.default_value(DEFAULT_TIME_RANGE)
.build(),
glib::ParamSpecBoolean::builder("flip-x").default_value(DEFAULT_FLIP_X).build(),
glib::ParamSpecBoolean::builder("flip-y").default_value(DEFAULT_FLIP_Y).build(),
glib::ParamSpecBoolean::builder("vertical")
.default_value(DEFAULT_VERTICAL)
.build(),
glib::ParamSpecEnum::builder::<RenderType>("type")
.default_value(RenderType::default())
.build(),
glib::ParamSpecBoolean::builder("animate")
.default_value(DEFAULT_ANIMATE)
.build(),
]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
let needs_path_rebuild = match pspec.name() {
"value" => {
let v: f64 = value.get().unwrap();
self.value.set(v);
// Don't record first value (default value)
if self.has_received_value.get() {
self.update_history((Instant::now(), v));
} else {
self.has_received_value.replace(true);
}
true
}
"thickness" => {
self.thickness.set(value.get().unwrap());
false // Thickness doesn't affect path geometry
}
"line-style" => {
self.line_style.set(value.get::<LineStyle>().unwrap());
false // Line style doesn't affect path geometry
}
"min" => {
self.min.set(value.get().unwrap());
true
}
"max" => {
self.max.set(value.get().unwrap());
true
}
"dynamic" => {
self.dynamic.set(value.get().unwrap());
true
}
"time-range" => {
self.time_range.set(value.get().unwrap());
true
}
"flip-x" => {
self.flip_x.set(value.get().unwrap());
true
}
"flip-y" => {
self.flip_y.set(value.get().unwrap());
true
}
"vertical" => {
self.vertical.set(value.get().unwrap());
true
}
"type" => {
self.render_type.set(value.get::<RenderType>().unwrap());
true
}
"animate" => {
let animate = value.get().unwrap();
self.set_animate(&self.obj(), animate);
false
}
x => panic!("Tried to set inexistent property of Graph: {}", x),
};
if needs_path_rebuild {
self.cached_path.replace(None);
}
// Queue redraw for any property change
self.obj().queue_draw();
}
fn property(&self, _: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"value" => self.value.get().to_value(),
"thickness" => self.thickness.get().to_value(),
"line-style" => self.line_style.get().to_value(),
"min" => self.min.get().to_value(),
"max" => self.max.get().to_value(),
"dynamic" => self.dynamic.get().to_value(),
"time-range" => (self.time_range.get()).to_value(),
"flip-x" => self.flip_x.get().to_value(),
"flip-y" => self.flip_y.get().to_value(),
"vertical" => self.vertical.get().to_value(),
"type" => self.render_type.get().to_value(),
"animate" => self.is_animate().to_value(),
x => panic!("Tried to get inexistent property of Graph: {}", x),
}
}
}
struct GraphGeometry {
width: f32,
height: f32,
flip_x: bool,
flip_y: bool,
vertical: bool,
}
struct GraphRange {
min: f64,
max: f64,
time_range: f32,
anchor_time: Instant,
}
struct GraphStyle {
render_type: RenderType,
thickness: f32,
line_style: LineStyle,
color: gdk::RGBA,
animate: bool,
}
impl WidgetImpl for Graph {
fn measure(&self, _orientation: gtk4::Orientation, _for_size: i32) -> (i32, i32, i32, i32) {
let t = self.thickness.get().max(1.0) as i32;
// min, natural, -, -
(t, t * 4, -1, -1)
}
/// Snapshot render nodes.
///
/// Since this is potentially a hot code path, we want to keep it O(N) for maximum performance.
/// - min/max value calculation cached
/// - graph curve path cached
fn snapshot(&self, snapshot: &gtk4::Snapshot) {
let obj = self.obj();
let history = self.history.borrow();
if history.is_empty() {
return;
}
// Margins
let margin_start = obj.margin_start() as f32;
let margin_end = obj.margin_end() as f32;
let margin_top = obj.margin_top() as f32;
let margin_bottom = obj.margin_bottom() as f32;
// Allocated size
let total_width = obj.width() as f32;
let total_height = obj.height() as f32;
let width = (total_width - margin_start - margin_end).max(0.0);
let height = (total_height - margin_top - margin_bottom).max(0.0);
let geom = GraphGeometry {
width,
height,
flip_x: self.flip_x.get(),
flip_y: self.flip_y.get(),
vertical: self.vertical.get(),
};
let style = GraphStyle {
render_type: self.render_type.get(),
thickness: self.thickness.get() as f32,
line_style: self.line_style.get(),
color: obj.color(),
animate: self.is_animate(),
};
let time_range_millis = self.time_range.get();
if time_range_millis == 0 {
return;
}
let render_time = Instant::now();
let time_range = time_range_millis as f32;
// Rebuild path?
let current_size = (width, height);
let mut cached_path = self.cached_path.borrow_mut();
if cached_path.is_none() || self.cached_path_size.get() != current_size {
self.cached_path_size.set(current_size);
self.path_anchor_time.set(render_time);
let range = GraphRange {
min: self.min_value(),
max: self.max_value(),
time_range,
anchor_time: render_time,
};
let new_path = build_path(&history, &geom, &range, &style);
*cached_path = Some(new_path);
}
// Draw the cached path with translation
snapshot.save();
snapshot.translate(&graphene::Point::new(margin_start, margin_top));
snapshot.push_clip(&graphene::Rect::new(0.0, 0.0, width, height));
// Scroll animation
if style.animate && history.len() >= 2 {
// Shift by point interval to hide the gap between latest point and right edge
let interval = {
let t_old = history[history.len() - 2].0;
let t_new = history[history.len() - 1].0;
t_new.checked_duration_since(t_old).unwrap_or_default()
}
.as_millis() as f32;
// Calculate pixel shift based on time
let anchor_time = self.path_anchor_time.get();
let time_shift = render_time.duration_since(anchor_time).as_millis() as f32;
let pixel_shift = (time_shift - interval) / time_range
* if geom.vertical { geom.height } else { geom.width }
* if geom.flip_x { 1.0 } else { -1.0 };
// Apply the camera translation
if geom.vertical {
snapshot.translate(&graphene::Point::new(0.0, pixel_shift));
} else {
snapshot.translate(&graphene::Point::new(pixel_shift, 0.0));
}
};
// Draw path
if let Some(path) = cached_path.as_ref() {
if matches!(style.render_type, RenderType::Line | RenderType::StepLine) {
// Render as stroked path
let stroke = gsk::Stroke::new(style.thickness);
// Configure line style
match style.line_style {
LineStyle::Miter => {
stroke.set_line_cap(gsk::LineCap::Butt);
stroke.set_line_join(gsk::LineJoin::Miter);
}
LineStyle::Bevel => {
stroke.set_line_cap(gsk::LineCap::Square);
stroke.set_line_join(gsk::LineJoin::Bevel);
}
LineStyle::Round => {
stroke.set_line_cap(gsk::LineCap::Round);
stroke.set_line_join(gsk::LineJoin::Round);
}
}
snapshot.append_stroke(path, &stroke, &style.color);
} else {
// Render as filled path
snapshot.append_fill(path, gsk::FillRule::Winding, &style.color);
}
}
snapshot.pop(); // pop clip
snapshot.restore(); // restore translate
}
}
impl Graph {
// Updates the history, removing points outside the range
fn update_history(&self, v: (Instant, f64)) {
let mut history = self.history.borrow_mut();
let now = Instant::now();
self.last_updated_at.set(now);
let time_range_dur = Duration::from_millis(self.time_range.get().into());
let visible_start = now
.checked_sub(time_range_dur)
.unwrap_or_else(|| history.front().map(|(t, _)| *t).unwrap_or(now));
// Animate: Need one extra point more left of the visible area to avoid showing a gap
let points_to_keep_off_canvas = if self.is_animate() { 2 } else { 1 };
// Prune history from the front (oldest).
while history.len() > points_to_keep_off_canvas {
// We check the timestamp of the point at index `points_to_keep_off_canvas`.
// If that point is outside the canvas, we can safely remove the oldest point (index 0).
if history[points_to_keep_off_canvas].0 < visible_start {
if let Some(val) = history.pop_front() {
// Value dropped: min/max values need recalc?
if self.min_value_cached.get().map_or(false, |min| min == val.1) {
self.min_value_cached.set(None);
}
if self.max_value_cached.get().map_or(false, |max| max == val.1) {
self.max_value_cached.set(None);
}
}
} else {
break;
}
}
history.push_back(v);
// New value: Update cached min/max value?
if let Some(min) = self.min_value_cached.get() {
self.min_value_cached.set(Some(min.min(v.1)));
}
if let Some(max) = self.max_value_cached.get() {
self.max_value_cached.set(Some(max.max(v.1)));
}
}
fn set_animate(&self, obj: &super::Graph, animate: bool) {
let mut tick_id_storage = self.tick_id.borrow_mut();
if animate {
// If we want it ON, and it's currently None (OFF)
if tick_id_storage.is_none() {
let id = obj.add_tick_callback(|obj, _clock| {
obj.queue_draw();
glib::ControlFlow::Continue
});
*tick_id_storage = Some(id);
}
} else {
break;
// We want it OFF
if let Some(id) = tick_id_storage.take() {
id.remove();
}
}
}
history.push_back(v);
}
/**
* Receives normalized (0-1) coordinates `x` and `y` and convert them to the
* point on the widget.
*/
fn value_to_point(&self, width: f64, height: f64, x: f64, y: f64) -> (f64, f64) {
let x = if *self.flip_x.borrow() { 1.0 - x } else { x };
let y = if *self.flip_y.borrow() { 1.0 - y } else { y };
let (x, y) = if *self.vertical.borrow() { (y, x) } else { (x, y) };
(width * x, height * y)
}
}
impl ObjectImpl for GraphPriv {
fn properties() -> &'static [glib::ParamSpec] {
Self::derived_properties()
}
fn is_animate(&self) -> bool {
self.tick_id.borrow().is_some()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"value" => {
let value = value.get().unwrap();
self.value.replace(value);
self.update_history((std::time::Instant::now(), value));
self.obj().queue_draw();
fn min_value(&self) -> f64 {
if self.dynamic.get() {
self.min_value_cached.get().unwrap_or_else(|| {
// Calculate from history values
let history = self.history.borrow();
let val = history.iter().fold(f64::INFINITY, |acc, &(_, v)| acc.min(v));
self.min_value_cached.set(Some(val));
val
})
} else {
self.min.get()
}
"thickness" => {
self.thickness.replace(value.get().unwrap());
}
fn max_value(&self) -> f64 {
if self.dynamic.get() {
self.max_value_cached.get().unwrap_or_else(|| {
// Calculate from history values
let history = self.history.borrow();
let val = history.iter().fold(f64::NEG_INFINITY, |acc, &(_, v)| acc.max(v));
self.max_value_cached.set(Some(val));
val
})
} else {
self.max.get()
}
"max" => {
self.max.replace(value.get().unwrap());
}
"min" => {
self.min.replace(value.get().unwrap());
}
"dynamic" => {
self.dynamic.replace(value.get().unwrap());
}
"time-range" => {
self.time_range.replace(value.get().unwrap());
}
"line-style" => {
self.line_style.replace(value.get().unwrap());
}
"flip-x" => {
self.flip_x.replace(value.get().unwrap());
}
"flip-y" => {
self.flip_y.replace(value.get().unwrap());
}
"vertical" => {
self.vertical.replace(value.get().unwrap());
}
x => panic!("Tried to set inexistant property of Graph: {}", x,),
}
}
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
self.derived_property(id, pspec)
/// Builds a path relative to a fixed anchor time
fn build_path(
points: &VecDeque<(Instant, f64)>,
geom: &GraphGeometry,
range: &GraphRange,
style: &GraphStyle,
) -> gsk::Path {
if points.is_empty() {
return gsk::PathBuilder::new().to_path();
}
if range.time_range <= 0.0 {
return gsk::PathBuilder::new().to_path();
}
let builder = gsk::PathBuilder::new();
let is_line = matches!(style.render_type, RenderType::Line | RenderType::StepLine);
let is_step = matches!(style.render_type, RenderType::StepLine | RenderType::StepFill);
// Transform point relative to fixed anchor
let transform_point = |t_point: Instant, value: f64| -> (f32, f32) {
let t = range.anchor_time.duration_since(t_point).as_millis() as f32;
let nx = t / range.time_range;
let ny = ((value - range.min) / (range.max - range.min).max(f64::EPSILON)) as f32;
let x = if geom.flip_x { nx } else { 1.0 - nx };
let y = if geom.flip_y { ny } else { 1.0 - ny };
if geom.vertical {
(y * geom.width, x * geom.height)
} else {
(x * geom.width, y * geom.height)
}
};
// Calculate first point coordinates
let &(t0, v0) = &points[0];
let (mut last_x, mut last_y) = transform_point(t0, v0);
let (base_x, base_y) = transform_point(range.anchor_time, range.min);
if is_line {
// For lines: start at first point
builder.move_to(last_x, last_y);
} else {
// For fills: start at baseline then go to first point
if geom.vertical {
builder.move_to(base_x, last_y);
builder.line_to(last_x, last_y);
} else {
builder.move_to(last_x, base_y);
builder.line_to(last_x, last_y);
}
}
// Draw the main graph line
for i in 1..points.len() {
let &(_, v_prev) = &points[i - 1];
let &(t_curr, v_curr) = &points[i];
let (x_curr, y_curr) = transform_point(t_curr, v_curr);
if is_step {
// Create horizontal step segment
let (x_step, y_step) = transform_point(t_curr, v_prev);
builder.line_to(x_step, y_step);
}
builder.line_to(x_curr, y_curr);
last_x = x_curr;
last_y = y_curr;
}
if is_line {
// For lines, we're done
builder.to_path()
} else {
// For fills, close the path to baseline
if geom.vertical {
builder.line_to(base_x, last_y);
} else {
builder.line_to(last_x, base_y);
}
builder.close();
builder.to_path()
}
}
}
#[object_subclass]
impl ObjectSubclass for GraphPriv {
type ParentType = gtk::Bin;
type Type = Graph;
const NAME: &'static str = "Graph";
fn class_init(klass: &mut Self::Class) {
klass.set_css_name("graph");
}
#[derive(glib::Enum, Debug, Clone, Copy, PartialEq, Eq, Default)]
#[enum_type(name = "EwwiiGraphLineStyle")]
pub enum LineStyle {
#[default]
Miter,
Bevel,
Round,
}
impl Default for Graph {
fn default() -> Self {
Self::new()
}
#[derive(glib::Enum, Debug, Clone, Copy, PartialEq, Eq, Default)]
#[enum_type(name = "EwwiiGraphRenderType")]
pub enum RenderType {
#[default]
Line,
StepLine,
Fill,
StepFill,
}
// public wrapper
glib::wrapper! {
pub struct Graph(ObjectSubclass<imp::Graph>)
@extends gtk4::Widget,
@implements gtk4::Accessible, gtk4::Actionable, gtk4::Buildable, gtk4::ConstraintTarget;
}
impl Graph {
pub fn new() -> Self {
glib::Object::new::<Self>()
glib::Object::builder().build()
}
}
impl ContainerImpl for GraphPriv {
fn add(&self, _widget: &gtk::Widget) {
error_handling_ctx::print_error(anyhow!("Error, Graph widget shoudln't have any children"));
}
}
impl BinImpl for GraphPriv {}
impl WidgetImpl for GraphPriv {
fn preferred_width(&self) -> (i32, i32) {
let thickness = *self.thickness.borrow() as i32;
(thickness, thickness)
}
fn preferred_width_for_height(&self, height: i32) -> (i32, i32) {
(height, height)
}
fn preferred_height(&self) -> (i32, i32) {
let thickness = *self.thickness.borrow() as i32;
(thickness, thickness)
}
fn preferred_height_for_width(&self, width: i32) -> (i32, i32) {
(width, width)
}
fn draw(&self, cr: &cairo::Context) -> glib::Propagation {
let res: Result<()> = (|| {
let history = &*self.history.borrow();
let extra_point = *self.extra_point.borrow();
// Calculate the max value
let (min, max) = {
let mut max = *self.max.borrow();
let min = *self.min.borrow();
let dynamic = *self.dynamic.borrow();
if dynamic {
// Check for points higher than max
for (_, value) in history {
if *value > max {
max = *value;
}
}
if let Some((_, value)) = extra_point {
if value > max {
max = value;
}
}
}
(min, max)
};
let styles = self.obj().style_context();
let (margin_top, margin_right, margin_bottom, margin_left) = {
let margin = styles.margin(gtk::StateFlags::NORMAL);
(margin.top as f64, margin.right as f64, margin.bottom as f64, margin.left as f64)
};
let width = self.obj().allocated_width() as f64 - margin_left - margin_right;
let height = self.obj().allocated_height() as f64 - margin_top - margin_bottom;
// Calculate graph points once
// Separating this into another function would require pasing a
// GraphPriv that would hide interior mutability
let points = {
let value_range = max - min;
let time_range = *self.time_range.borrow() as f64;
let last_updated_at = self.last_updated_at.borrow();
let mut points = history
.iter()
.map(|(instant, value)| {
let t = last_updated_at.duration_since(*instant).as_millis() as f64;
self.value_to_point(
width,
height,
t / time_range,
(value - min) / value_range,
)
})
.collect::<VecDeque<(f64, f64)>>();
// Aad an extra point outside of the graph to extend the line to the left
if let Some((instant, value)) = extra_point {
let t = last_updated_at.duration_since(instant).as_millis() as f64;
let (x, y) = self.value_to_point(
width,
height,
(t - time_range) / time_range,
(value - min) / value_range,
);
points.push_front(if *self.vertical.borrow() { (x, -y) } else { (-x, y) });
}
points
};
// Actually draw the graph
cr.save()?;
cr.translate(margin_left, margin_top);
cr.rectangle(0.0, 0.0, width, height);
cr.clip();
// Draw Background
let bg_color: gdk::RGBA = styles
.style_property_for_state("background-color", gtk::StateFlags::NORMAL)
.get()?;
if bg_color.alpha() > 0.0 {
if let Some(first_point) = points.front() {
cr.line_to(first_point.0, height + margin_bottom);
}
for (x, y) in points.iter() {
cr.line_to(*x, *y);
}
cr.line_to(width, height);
cr.set_source_rgba(
bg_color.red(),
bg_color.green(),
bg_color.blue(),
bg_color.alpha(),
);
cr.fill()?;
}
// Draw Line
let line_color: gdk::RGBA = styles.color(gtk::StateFlags::NORMAL);
let thickness = *self.thickness.borrow();
if line_color.alpha() > 0.0 && thickness > 0.0 {
for (x, y) in points.iter() {
cr.line_to(*x, *y);
}
let line_style = &*self.line_style.borrow();
apply_line_style(line_style.as_str(), cr)?;
cr.set_line_width(thickness);
cr.set_source_rgba(
line_color.red(),
line_color.green(),
line_color.blue(),
line_color.alpha(),
);
cr.stroke()?;
}
cr.reset_clip();
cr.restore()?;
Ok(())
})();
if let Err(error) = res {
error_handling_ctx::print_error(error)
};
glib::Propagation::Proceed
}
}
fn apply_line_style(style: &str, cr: &cairo::Context) -> Result<()> {
match style {
"miter" => {
cr.set_line_cap(cairo::LineCap::Butt);
cr.set_line_join(cairo::LineJoin::Miter);
}
"bevel" => {
cr.set_line_cap(cairo::LineCap::Square);
cr.set_line_join(cairo::LineJoin::Bevel);
}
"round" => {
cr.set_line_cap(cairo::LineCap::Round);
cr.set_line_join(cairo::LineJoin::Round);
}
_ => Err(anyhow!("Error, the value: {} for atribute join is not valid", style))?,
};
Ok(())
}

View File

@@ -4,4 +4,3 @@ pub mod graph;
pub mod transform;
pub mod widget_definitions;
pub mod widget_definitions_helper;
pub mod window;

View File

@@ -1,214 +1,214 @@
use crate::window::coords::NumWithUnit;
use anyhow::{anyhow, Result};
use gtk::glib::{self, object_subclass, wrapper, Properties};
use gtk::{prelude::*, subclass::prelude::*};
use std::{cell::RefCell, str::FromStr};
// use crate::window::coords::NumWithUnit;
// use anyhow::{anyhow, Result};
// use gtk4::glib::{self, object_subclass, wrapper, Properties};
// use gtk4::{prelude::*, subclass::prelude::*};
// use std::{cell::RefCell, str::FromStr};
use crate::error_handling_ctx;
// use crate::error_handling_ctx;
wrapper! {
pub struct Transform(ObjectSubclass<TransformPriv>)
@extends gtk::Bin, gtk::Container, gtk::Widget;
}
// wrapper! {
// pub struct Transform(ObjectSubclass<TransformPriv>)
// @extends gtk4::Bin, gtk4::Container, gtk4::Widget;
// }
#[derive(Properties)]
#[properties(wrapper_type = Transform)]
pub struct TransformPriv {
#[property(get, set, nick = "Rotate", blurb = "The Rotation", minimum = f64::MIN, maximum = f64::MAX, default = 0f64)]
rotate: RefCell<f64>,
// #[derive(Properties)]
// #[properties(wrapper_type = Transform)]
// pub struct TransformPriv {
// #[property(get, set, nick = "Rotate", blurb = "The Rotation", minimum = f64::MIN, maximum = f64::MAX, default = 0f64)]
// rotate: RefCell<f64>,
#[property(get, set, nick = "Transform-Origin X", blurb = "X coordinate (%/px) for the Transform-Origin", default = None)]
transform_origin_x: RefCell<Option<String>>,
// #[property(get, set, nick = "Transform-Origin X", blurb = "X coordinate (%/px) for the Transform-Origin", default = None)]
// transform_origin_x: RefCell<Option<String>>,
#[property(get, set, nick = "Transform-Origin Y", blurb = "Y coordinate (%/px) for the Transform-Origin", default = None)]
transform_origin_y: RefCell<Option<String>>,
// #[property(get, set, nick = "Transform-Origin Y", blurb = "Y coordinate (%/px) for the Transform-Origin", default = None)]
// transform_origin_y: RefCell<Option<String>>,
#[property(get, set, nick = "Translate x", blurb = "The X Translation", default = None)]
translate_x: RefCell<Option<String>>,
// #[property(get, set, nick = "Translate x", blurb = "The X Translation", default = None)]
// translate_x: RefCell<Option<String>>,
#[property(get, set, nick = "Translate y", blurb = "The Y Translation", default = None)]
translate_y: RefCell<Option<String>>,
// #[property(get, set, nick = "Translate y", blurb = "The Y Translation", default = None)]
// translate_y: RefCell<Option<String>>,
#[property(get, set, nick = "Scale x", blurb = "The amount to scale in x", default = None)]
scale_x: RefCell<Option<String>>,
// #[property(get, set, nick = "Scale x", blurb = "The amount to scale in x", default = None)]
// scale_x: RefCell<Option<String>>,
#[property(get, set, nick = "Scale y", blurb = "The amount to scale in y", default = None)]
scale_y: RefCell<Option<String>>,
// #[property(get, set, nick = "Scale y", blurb = "The amount to scale in y", default = None)]
// scale_y: RefCell<Option<String>>,
content: RefCell<Option<gtk::Widget>>,
}
// content: RefCell<Option<gtk4::Widget>>,
// }
// This should match the default values from the ParamSpecs
impl Default for TransformPriv {
fn default() -> Self {
TransformPriv {
rotate: RefCell::new(0.0),
transform_origin_x: RefCell::new(None),
transform_origin_y: RefCell::new(None),
translate_x: RefCell::new(None),
translate_y: RefCell::new(None),
scale_x: RefCell::new(None),
scale_y: RefCell::new(None),
content: RefCell::new(None),
}
}
}
// // This should match the default values from the ParamSpecs
// impl Default for TransformPriv {
// fn default() -> Self {
// TransformPriv {
// rotate: RefCell::new(0.0),
// transform_origin_x: RefCell::new(None),
// transform_origin_y: RefCell::new(None),
// translate_x: RefCell::new(None),
// translate_y: RefCell::new(None),
// scale_x: RefCell::new(None),
// scale_y: RefCell::new(None),
// content: RefCell::new(None),
// }
// }
// }
impl ObjectImpl for TransformPriv {
fn properties() -> &'static [glib::ParamSpec] {
Self::derived_properties()
}
// impl ObjectImpl for TransformPriv {
// fn properties() -> &'static [glib::ParamSpec] {
// Self::derived_properties()
// }
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"rotate" => {
self.rotate.replace(value.get().unwrap());
self.obj().queue_draw(); // Queue a draw call with the updated value
}
"transform-origin-x" => {
self.transform_origin_x.replace(value.get().unwrap());
self.obj().queue_draw(); // Queue a draw call with the updated value
}
"transform-origin-y" => {
self.transform_origin_y.replace(value.get().unwrap());
self.obj().queue_draw(); // Queue a draw call with the updated value
}
"translate-x" => {
self.translate_x.replace(value.get().unwrap());
self.obj().queue_draw(); // Queue a draw call with the updated value
}
"translate-y" => {
self.translate_y.replace(value.get().unwrap());
self.obj().queue_draw(); // Queue a draw call with the updated value
}
"scale-x" => {
self.scale_x.replace(value.get().unwrap());
self.obj().queue_draw(); // Queue a draw call with the updated value
}
"scale-y" => {
self.scale_y.replace(value.get().unwrap());
self.obj().queue_draw(); // Queue a draw call with the updated value
}
x => panic!("Tried to set inexistant property of Transform: {}", x,),
}
}
// fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
// match pspec.name() {
// "rotate" => {
// self.rotate.replace(value.get().unwrap());
// self.obj().queue_draw(); // Queue a draw call with the updated value
// }
// "transform-origin-x" => {
// self.transform_origin_x.replace(value.get().unwrap());
// self.obj().queue_draw(); // Queue a draw call with the updated value
// }
// "transform-origin-y" => {
// self.transform_origin_y.replace(value.get().unwrap());
// self.obj().queue_draw(); // Queue a draw call with the updated value
// }
// "translate-x" => {
// self.translate_x.replace(value.get().unwrap());
// self.obj().queue_draw(); // Queue a draw call with the updated value
// }
// "translate-y" => {
// self.translate_y.replace(value.get().unwrap());
// self.obj().queue_draw(); // Queue a draw call with the updated value
// }
// "scale-x" => {
// self.scale_x.replace(value.get().unwrap());
// self.obj().queue_draw(); // Queue a draw call with the updated value
// }
// "scale-y" => {
// self.scale_y.replace(value.get().unwrap());
// self.obj().queue_draw(); // Queue a draw call with the updated value
// }
// x => panic!("Tried to set inexistant property of Transform: {}", x,),
// }
// }
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
self.derived_property(id, pspec)
}
}
// fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
// self.derived_property(id, pspec)
// }
// }
#[object_subclass]
impl ObjectSubclass for TransformPriv {
type ParentType = gtk::Bin;
type Type = Transform;
// #[object_subclass]
// impl ObjectSubclass for TransformPriv {
// type ParentType = gtk4::Bin;
// type Type = Transform;
const NAME: &'static str = "Transform";
// const NAME: &'static str = "Transform";
fn class_init(klass: &mut Self::Class) {
klass.set_css_name("transform");
}
}
// fn class_init(klass: &mut Self::Class) {
// klass.set_css_name("transform");
// }
// }
impl Default for Transform {
fn default() -> Self {
Self::new()
}
}
// impl Default for Transform {
// fn default() -> Self {
// Self::new()
// }
// }
impl Transform {
pub fn new() -> Self {
glib::Object::new::<Self>()
}
}
// impl Transform {
// pub fn new() -> Self {
// glib::Object::new::<Self>()
// }
// }
impl ContainerImpl for TransformPriv {
fn add(&self, widget: &gtk::Widget) {
if let Some(content) = &*self.content.borrow() {
// TODO: Handle this error when populating children widgets instead
error_handling_ctx::print_error(anyhow!(
"Error, trying to add multiple children to a circular-progress widget"
));
self.parent_remove(content);
}
self.parent_add(widget);
self.content.replace(Some(widget.clone()));
}
}
// impl ContainerImpl for TransformPriv {
// fn add(&self, widget: &gtk4::Widget) {
// if let Some(content) = &*self.content.borrow() {
// // TODO: Handle this error when populating children widgets instead
// error_handling_ctx::print_error(anyhow!(
// "Error, trying to add multiple children to a circular-progress widget"
// ));
// self.parent_remove(content);
// }
// self.parent_add(widget);
// self.content.replace(Some(widget.clone()));
// }
// }
impl BinImpl for TransformPriv {}
impl WidgetImpl for TransformPriv {
fn draw(&self, cr: &gtk::cairo::Context) -> glib::Propagation {
let res: Result<()> = (|| {
let rotate = *self.rotate.borrow();
let total_width = self.obj().allocated_width() as f64;
let total_height = self.obj().allocated_height() as f64;
// impl BinImpl for TransformPriv {}
// impl WidgetImpl for TransformPriv {
// fn draw(&self, cr: &gtk4::cairo::Context) -> glib::Propagation {
// let res: Result<()> = (|| {
// let rotate = *self.rotate.borrow();
// let total_width = self.obj().allocated_width() as f64;
// let total_height = self.obj().allocated_height() as f64;
cr.save()?;
// cr.save()?;
let transform_origin_x = match &*self.transform_origin_x.borrow() {
Some(rcx) => {
NumWithUnit::from_str(rcx)?.pixels_relative_to(total_width as i32) as f64
}
None => 0.0,
};
let transform_origin_y = match &*self.transform_origin_y.borrow() {
Some(rcy) => {
NumWithUnit::from_str(rcy)?.pixels_relative_to(total_height as i32) as f64
}
None => 0.0,
};
// let transform_origin_x = match &*self.transform_origin_x.borrow() {
// Some(rcx) => {
// NumWithUnit::from_str(rcx)?.pixels_relative_to(total_width as i32) as f64
// }
// None => 0.0,
// };
// let transform_origin_y = match &*self.transform_origin_y.borrow() {
// Some(rcy) => {
// NumWithUnit::from_str(rcy)?.pixels_relative_to(total_height as i32) as f64
// }
// None => 0.0,
// };
let translate_x = match &*self.translate_x.borrow() {
Some(tx) => {
NumWithUnit::from_str(tx)?.pixels_relative_to(total_width as i32) as f64
}
None => 0.0,
};
// let translate_x = match &*self.translate_x.borrow() {
// Some(tx) => {
// NumWithUnit::from_str(tx)?.pixels_relative_to(total_width as i32) as f64
// }
// None => 0.0,
// };
let translate_y = match &*self.translate_y.borrow() {
Some(ty) => {
NumWithUnit::from_str(ty)?.pixels_relative_to(total_height as i32) as f64
}
None => 0.0,
};
// let translate_y = match &*self.translate_y.borrow() {
// Some(ty) => {
// NumWithUnit::from_str(ty)?.pixels_relative_to(total_height as i32) as f64
// }
// None => 0.0,
// };
let scale_x = match &*self.scale_x.borrow() {
Some(sx) => {
NumWithUnit::from_str(sx)?.perc_relative_to(total_width as i32) as f64 / 100.0
}
None => 1.0,
};
// let scale_x = match &*self.scale_x.borrow() {
// Some(sx) => {
// NumWithUnit::from_str(sx)?.perc_relative_to(total_width as i32) as f64 / 100.0
// }
// None => 1.0,
// };
let scale_y = match &*self.scale_y.borrow() {
Some(sy) => {
NumWithUnit::from_str(sy)?.perc_relative_to(total_height as i32) as f64 / 100.0
}
None => 1.0,
};
// let scale_y = match &*self.scale_y.borrow() {
// Some(sy) => {
// NumWithUnit::from_str(sy)?.perc_relative_to(total_height as i32) as f64 / 100.0
// }
// None => 1.0,
// };
cr.translate(transform_origin_x, transform_origin_y);
cr.rotate(perc_to_rad(rotate));
cr.translate(translate_x - transform_origin_x, translate_y - transform_origin_y);
cr.scale(scale_x, scale_y);
// cr.translate(transform_origin_x, transform_origin_y);
// cr.rotate(perc_to_rad(rotate));
// cr.translate(translate_x - transform_origin_x, translate_y - transform_origin_y);
// cr.scale(scale_x, scale_y);
// Children widget
if let Some(child) = &*self.content.borrow() {
self.obj().propagate_draw(child, cr);
}
// // Children widget
// if let Some(child) = &*self.content.borrow() {
// self.obj().propagate_draw(child, cr);
// }
cr.restore()?;
Ok(())
})();
// cr.restore()?;
// Ok(())
// })();
if let Err(error) = res {
error_handling_ctx::print_error(error)
};
// if let Err(error) = res {
// error_handling_ctx::print_error(error)
// };
glib::Propagation::Proceed
}
}
// glib::Propagation::Proceed
// }
// }
fn perc_to_rad(n: f64) -> f64 {
(n / 100f64) * 2f64 * std::f64::consts::PI
}
// fn perc_to_rad(n: f64) -> f64 {
// (n / 100f64) * 2f64 * std::f64::consts::PI
// }

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,24 @@
use crate::gtk::prelude::LabelExt;
use anyhow::{anyhow, Result};
use gtk::pango;
use gtk4::glib;
use gtk4::glib::gobject_ffi;
use gtk4::glib::translate::{FromGlibPtrNone, IntoGlib};
use gtk4::glib::Value;
use gtk4::pango;
use gtk4::prelude::{Cast, ObjectExt, RangeExt, StaticType, ToValue};
use rhai::Map;
use std::process::Command;
use crate::widgets::graph::{LineStyle, RenderType};
// Run a command and get the output
pub(super) fn run_command<T>(timeout: std::time::Duration, cmd: &str, args: &[T])
where
T: 'static + std::fmt::Display + Send + Sync + Clone,
{
if cmd.is_empty() {
return;
}
use wait_timeout::ChildExt;
let cmd = replace_placeholders(cmd, args);
std::thread::Builder::new()
@@ -75,29 +85,29 @@ pub fn dynamic_eq(a: &rhai::Dynamic, b: &rhai::Dynamic) -> bool {
}
/// ALL WIDGETS
pub(super) fn parse_align(o: &str) -> Result<gtk::Align> {
pub(super) fn parse_align(o: &str) -> Result<gtk4::Align> {
match o.to_ascii_lowercase().as_str() {
"fill" => Ok(gtk::Align::Fill),
"baseline" => Ok(gtk::Align::Baseline),
"center" => Ok(gtk::Align::Center),
"start" => Ok(gtk::Align::Start),
"end" => Ok(gtk::Align::End),
"fill" => Ok(gtk4::Align::Fill),
"baseline" => Ok(gtk4::Align::Baseline),
"center" => Ok(gtk4::Align::Center),
"start" => Ok(gtk4::Align::Start),
"end" => Ok(gtk4::Align::End),
other => Err(anyhow!("Invalid alignment: {}", other)),
}
}
/// Gtk Box
pub(super) fn parse_orientation(ori: &str) -> Result<gtk::Orientation> {
pub(super) fn parse_orientation(ori: &str) -> Result<gtk4::Orientation> {
match ori.to_ascii_lowercase().as_str() {
"h" | "horizontal" => Ok(gtk::Orientation::Horizontal),
"v" | "vertical" => Ok(gtk::Orientation::Vertical),
"h" | "horizontal" => Ok(gtk4::Orientation::Horizontal),
"v" | "vertical" => Ok(gtk4::Orientation::Vertical),
other => Err(anyhow!("Invalid orientation: {}", other)),
}
}
/// Gtk Label
pub(super) fn apply_ellipsize_settings(
label: &gtk::Label,
label: &gtk4::Label,
truncate: bool,
limit_width: i32,
truncate_left: bool,
@@ -126,12 +136,12 @@ pub(super) fn parse_gravity(s: &str) -> Result<pango::Gravity> {
}
}
pub(super) fn parse_justification(s: &str) -> Result<gtk::Justification> {
pub(super) fn parse_justification(s: &str) -> Result<gtk4::Justification> {
match s.to_ascii_lowercase().as_str() {
"left" => Ok(gtk::Justification::Left),
"right" => Ok(gtk::Justification::Right),
"center" => Ok(gtk::Justification::Center),
"fill" => Ok(gtk::Justification::Fill),
"left" => Ok(gtk4::Justification::Left),
"right" => Ok(gtk4::Justification::Right),
"center" => Ok(gtk4::Justification::Center),
"fill" => Ok(gtk4::Justification::Fill),
_ => Err(anyhow!("Invalid justification: '{}'", s)),
}
}
@@ -146,12 +156,23 @@ pub(super) fn parse_wrap_mode(s: &str) -> Result<pango::WrapMode> {
}
/// Gtk scale (slider)
pub(super) fn parse_position_type(s: &str) -> Result<gtk::PositionType> {
pub(super) fn parse_position_type(s: &str) -> Result<gtk4::PositionType> {
match s.to_ascii_lowercase().as_str() {
"left" => Ok(gtk::PositionType::Left),
"right" => Ok(gtk::PositionType::Right),
"top" => Ok(gtk::PositionType::Top),
"bottom" => Ok(gtk::PositionType::Bottom),
"left" => Ok(gtk4::PositionType::Left),
"right" => Ok(gtk4::PositionType::Right),
"top" => Ok(gtk4::PositionType::Top),
"bottom" => Ok(gtk4::PositionType::Bottom),
_ => Err(anyhow!("Invalid position type: '{}'", s)),
}
}
/// Gtk flow box
pub(super) fn parse_selection_model(s: &str) -> Result<gtk4::SelectionMode> {
match s.to_ascii_lowercase().as_str() {
"none" => Ok(gtk4::SelectionMode::None),
"single" => Ok(gtk4::SelectionMode::Single),
"browse" => Ok(gtk4::SelectionMode::Browse),
"multiple" => Ok(gtk4::SelectionMode::Multiple),
_ => Err(anyhow!("Invalid position type: '{}'", s)),
}
}
@@ -172,32 +193,18 @@ where
}
/// Revealer
pub(super) fn parse_revealer_transition(t: &str) -> Result<gtk::RevealerTransitionType> {
pub(super) fn parse_revealer_transition(t: &str) -> Result<gtk4::RevealerTransitionType> {
match t.to_ascii_lowercase().as_str() {
"slideright" => Ok(gtk::RevealerTransitionType::SlideRight),
"slideleft" => Ok(gtk::RevealerTransitionType::SlideLeft),
"slideup" => Ok(gtk::RevealerTransitionType::SlideUp),
"slidedown" => Ok(gtk::RevealerTransitionType::SlideDown),
"fade" | "crossfade" => Ok(gtk::RevealerTransitionType::Crossfade),
"none" => Ok(gtk::RevealerTransitionType::None),
"slideright" => Ok(gtk4::RevealerTransitionType::SlideRight),
"slideleft" => Ok(gtk4::RevealerTransitionType::SlideLeft),
"slideup" => Ok(gtk4::RevealerTransitionType::SlideUp),
"slidedown" => Ok(gtk4::RevealerTransitionType::SlideDown),
"fade" | "crossfade" => Ok(gtk4::RevealerTransitionType::Crossfade),
"none" => Ok(gtk4::RevealerTransitionType::None),
_ => Err(anyhow!("Invalid transition: '{}'", t)),
}
}
/// Gtk Image
// icon-size - "menu", "small-toolbar", "toolbar", "large-toolbar", "button", "dnd", "dialog"
pub(super) fn parse_icon_size(o: &str) -> Result<gtk::IconSize> {
match o.to_ascii_lowercase().as_str() {
"menu" => Ok(gtk::IconSize::Menu),
"small-toolbar" | "toolbar" => Ok(gtk::IconSize::SmallToolbar),
"large-toolbar" => Ok(gtk::IconSize::LargeToolbar),
"button" => Ok(gtk::IconSize::Button),
"dnd" => Ok(gtk::IconSize::Dnd),
"dialog" => Ok(gtk::IconSize::Dialog),
_ => Err(anyhow!("Invalid icon size: '{}'", o)),
}
}
/// Event box
// dragtype - "file", "text"
pub(super) enum DragEntryType {
@@ -215,14 +222,137 @@ pub(super) fn parse_dragtype(o: &str) -> Result<DragEntryType> {
/// Stack widget
// transition - "slideright", "slideleft", "slideup", "slidedown", "crossfade", "none"
pub(super) fn parse_stack_transition(t: &str) -> Result<gtk::StackTransitionType> {
pub(super) fn parse_stack_transition(t: &str) -> Result<gtk4::StackTransitionType> {
match t.to_ascii_lowercase().as_str() {
"slideright" => Ok(gtk::StackTransitionType::SlideRight),
"slideleft" => Ok(gtk::StackTransitionType::SlideLeft),
"slideup" => Ok(gtk::StackTransitionType::SlideUp),
"slidedown" => Ok(gtk::StackTransitionType::SlideDown),
"fade" | "crossfade" => Ok(gtk::StackTransitionType::Crossfade),
"none" => Ok(gtk::StackTransitionType::None),
"slideright" => Ok(gtk4::StackTransitionType::SlideRight),
"slideleft" => Ok(gtk4::StackTransitionType::SlideLeft),
"slideup" => Ok(gtk4::StackTransitionType::SlideUp),
"slidedown" => Ok(gtk4::StackTransitionType::SlideDown),
"fade" | "crossfade" => Ok(gtk4::StackTransitionType::Crossfade),
"none" => Ok(gtk4::StackTransitionType::None),
_ => Err(anyhow!("Invalid stack transition: '{}'", t)),
}
}
/// Graph line style
pub(super) fn parse_graph_line_style(t: &str) -> Result<LineStyle> {
match t.to_ascii_lowercase().as_str() {
"miter" => Ok(LineStyle::Miter),
"bevel" => Ok(LineStyle::Bevel),
"round" => Ok(LineStyle::Round),
_ => Err(anyhow!("Invalid graph line style: '{}'", t)),
}
}
/// Graph render type
pub(super) fn parse_graph_render_type(t: &str) -> Result<RenderType> {
match t.to_ascii_lowercase().as_str() {
"line" => Ok(RenderType::Line),
"step-line" => Ok(RenderType::StepLine),
"fill" => Ok(RenderType::Fill),
"step-fill" => Ok(RenderType::StepFill),
_ => Err(anyhow!("Invalid graph render type: '{}'", t)),
}
}
// For localbind
pub(super) fn set_property_from_string_anywhere(
widget: &gtk4::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)),
}
}

View File

@@ -1,63 +0,0 @@
use gtk::glib::{self, object_subclass, wrapper, Properties};
use gtk::{prelude::*, subclass::prelude::*};
use std::cell::RefCell;
wrapper! {
pub struct Window(ObjectSubclass<WindowPriv>)
@extends gtk::Window, gtk::Bin, gtk::Container, gtk::Widget, @implements gtk::Buildable;
}
#[derive(Properties)]
#[properties(wrapper_type = Window)]
pub struct WindowPriv {
#[property(get, name = "x", nick = "X", blurb = "Global x coordinate", default = 0)]
x: RefCell<i32>,
#[property(get, name = "y", nick = "Y", blurb = "Global y coordinate", default = 0)]
y: RefCell<i32>,
}
// This should match the default values from the ParamSpecs
impl Default for WindowPriv {
fn default() -> Self {
WindowPriv { x: RefCell::new(0), y: RefCell::new(0) }
}
}
#[object_subclass]
impl ObjectSubclass for WindowPriv {
type ParentType = gtk::Window;
type Type = Window;
const NAME: &'static str = "WindowEwwii";
}
impl Default for Window {
fn default() -> Self {
glib::Object::new::<Self>()
}
}
impl Window {
pub fn new(type_: gtk::WindowType, x_: i32, y_: i32) -> Self {
let w: Self = glib::Object::builder().property("type", type_).build();
let priv_ = w.imp();
priv_.x.replace(x_);
priv_.y.replace(y_);
w
}
}
impl ObjectImpl for WindowPriv {
fn properties() -> &'static [glib::ParamSpec] {
Self::derived_properties()
}
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
self.derived_property(id, pspec)
}
}
impl WindowImpl for WindowPriv {}
impl BinImpl for WindowPriv {}
impl ContainerImpl for WindowPriv {}
impl WidgetImpl for WindowPriv {}

View File

@@ -33,12 +33,13 @@ impl NumWithUnit {
}
}
pub fn perc_relative_to(&self, max: i32) -> f32 {
match *self {
NumWithUnit::Percent(n) => n,
NumWithUnit::Pixels(n) => ((n as f64 / max as f64) * 100.0) as f32,
}
}
// add back when needed
// pub fn perc_relative_to(&self, max: i32) -> f32 {
// match *self {
// NumWithUnit::Percent(n) => n,
// NumWithUnit::Pixels(n) => ((n as f64 / max as f64) * 100.0) as f32,
// }
// }
}
impl FromStr for NumWithUnit {

View File

@@ -0,0 +1,23 @@
[package]
name = "ewwii_plugin_api"
version = "0.7.0"
authors = ["byson94 <byson94wastaken@gmail.com>"]
edition = "2021"
license = "GPL-3.0-or-later"
description = "A shared library for building plugins for ewwii"
repository = "https://github.com/byson94/ewwii"
homepage = "https://github.com/byson94/ewwii"
[features]
default = ["include-gtk4", "include-rhai"]
## Include the gtk4 dependency
include-gtk4 = ["dep:gtk4"]
## Include the rhai dependency
include-rhai = ["dep:rhai"]
[dependencies]
# rhai crate features should exactly match that of rhai_impl
rhai = { workspace = true, optional = true, features = ["internals"] }
gtk4 = { workspace = true, optional = true }

View File

@@ -0,0 +1,28 @@
# ewwii_plugin_api
A shared interface providing traits for building plugins for ewwii.
## Example
A simple example showing how to use this interface is provided below:
```rust
use ewwii_plugin_api::{EwwiiAPI, Plugin, export_plugin};
pub struct DummyStructure;
impl Plugin for DummyStructure {
// critical for ewwii to launch the plugin
fn init(&self, host: &dyn EwwiiAPI) {
// will be printed by the host
host.log("Plugin says Hello!");
host.rhai_engine_action(Box::new(|engine| {
let ast = engine.compile("1+1");
println!("Compiled AST: {:#?}", ast);
}));
}
}
// Critical for ewwii to see the plugin
export_plugin!(DummyStructure);
```

View File

@@ -0,0 +1,11 @@
//! A module providing example implementations of plugins
/// An example plugin that can be exported directly
pub struct ExamplePlugin;
impl crate::Plugin for ExamplePlugin {
/// Example code that initalizes the plugin
fn init(&self, host: &dyn crate::EwwiiAPI) {
host.log("Example plugin says Hello!");
}
}

View File

@@ -0,0 +1,63 @@
//! Module implementing macros
/// Macro to implement and export a plugin in a single step.
/// With this macro, users can write their plugin code directly
/// without having to manually implement each trait.
///
/// ## Example
///
/// The following example shows how you can use this macro to
/// easily make plugins in a single step.
///
/// ```rust,ignore
/// use ewwii_plugin_api::auto_plugin;
///
/// auto_plugin!(MyPluginName, {
/// // host variable is passed in automatically
/// host.log("Easy, huh?");
/// });
/// ```
///
/// ## When not to use it
///
/// This macro shall not be used if you prefer flexibility and safety.
/// The manual approach is verbose, but is way safer and flexible than using this macro.
#[macro_export]
macro_rules! auto_plugin {
($struct_name:ident, $init_block:block) => {
pub struct $struct_name;
// Implement the Plugin trait
impl $crate::Plugin for $struct_name {
fn init(&self, host: &dyn $crate::EwwiiAPI) {
$init_block
}
}
$crate::export_plugin!($struct_name);
};
}
/// Automatically implements `create_plugin` for a given fieldless structure
#[macro_export]
macro_rules! export_plugin {
($plugin_struct:path) => {
#[unsafe(no_mangle)]
pub extern "C" fn create_plugin() -> Box<dyn $crate::Plugin> {
Box::new($plugin_struct)
}
};
}
/// Automatically implements `create_plugin` for a given structure that has fields.
///
/// This macro expects the structure to have fields and also implement a `default()` method.
#[macro_export]
macro_rules! export_stateful_plugin {
($plugin_struct:path) => {
#[unsafe(no_mangle)]
pub extern "C" fn create_plugin() -> Box<dyn $crate::Plugin> {
Box::new(<$plugin_struct>::default())
}
};
}

View File

@@ -0,0 +1,180 @@
//! # ewwii_plugin_api - A plugin interface for ewwii
//!
//! `ewwii_plguin_api` is a shared list of traits
//! that both ewwii and its plugins can use.
//! This crate simplifies and provides a safe way for building
//! plugins for ewwii.
//!
//! ## Example
//!
//! The following example shows how this crate shall be used to build ewwii plugins:
//!
//! ```rust
//! use ewwii_plugin_api::{EwwiiAPI, Plugin, export_plugin};
//!
//! pub struct DummyStructure;
//!
//! impl Plugin for DummyStructure {
//! // critical for ewwii to launch the plugin
//! fn init(&self, host: &dyn EwwiiAPI) {
//! // will be printed by the host
//! host.log("Plugin says Hello!");
//! }
//! }
//!
//! // Critical for ewwii to load the plugin
//! export_plugin!(DummyStructure);
//! ```
mod export_macros;
pub mod example;
pub mod rhai_backend;
pub mod widget_backend;
#[cfg(feature = "include-rhai")]
pub use rhai;
#[cfg(feature = "include-gtk4")]
pub use gtk4;
/// The shared trait defining the Ewwii plugin API
pub trait EwwiiAPI: Send + Sync {
// == General Stuff == //
/// Print a message from the host
fn print(&self, msg: &str);
/// Log a message from the host
fn log(&self, msg: &str);
/// Log a warning from the host
fn warn(&self, msg: &str);
/// Log an error from the host
fn error(&self, msg: &str);
// == Rhai Manipulation Stuff == //
/// _(include-rhai)_ Perform actions on the latest rhai engine.
///
/// # Example
///
/// ```rust
/// use ewwii_plugin_api::{EwwiiAPI, Plugin};
///
/// pub struct DummyStructure;
///
/// impl Plugin for DummyStructure {
/// fn init(&self, host: &dyn EwwiiAPI) {
/// host.rhai_engine_action(Box::new(|eng| {
/// // eng = rhai::Engine
/// eng.set_max_expr_depths(128, 128);
/// }));
/// }
/// }
/// ```
#[cfg(feature = "include-rhai")]
fn rhai_engine_action(
&self,
f: Box<dyn FnOnce(&mut rhai::Engine) + Send>,
) -> Result<(), String>;
/// _(include-rhai)_ Expose a function that rhai configuration can call.
///
/// **NOTE:***
///
/// Due to TypeID mismatches, methods like `register_type`, `register_fn`,
/// etc. won't work on the engine and may cause a crash. It is recommended
/// to use the `register_function` API to register a funtion which `api::slib`
/// can call to in rhai.
///
/// # Example
///
/// ```rust
/// use ewwii_plugin_api::{EwwiiAPI, Plugin, rhai_backend::RhaiFnNamespace};
/// use rhai::Dynamic;
///
/// pub struct DummyStructure;
///
/// impl Plugin for DummyStructure {
/// fn init(&self, host: &dyn EwwiiAPI) {
/// host.register_function(
/// "my_func".to_string(),
/// RhaiFnNamespace::Global,
/// Box::new(|args| {
/// // Do stuff
/// // - Perform things on the args (if needed)
/// // - And return a value
///
/// Ok(Dynamic::default()) // return empty
/// }));
/// }
/// }
/// ```
///
/// This example will register a function with signature "my_func(Array)" in rhai.
///
/// ## Example use in rhai
///
/// ```js
/// print(my_func(["param1", "param2"]));
/// ```
#[cfg(feature = "include-rhai")]
fn register_function(
&self,
name: String,
namespace: rhai_backend::RhaiFnNamespace,
f: Box<
dyn Fn(rhai::Array) -> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> + Send + Sync,
>,
) -> Result<(), String>;
// == Widget Rendering & Logic == //
/// Get the list of all widget id's
fn list_widget_ids(&self) -> Result<Vec<u64>, String>;
/// _(include-gtk4)_ Perform actions on the latest widget registry.
///
/// # Example
///
/// ```rust
/// use ewwii_plugin_api::{EwwiiAPI, Plugin};
///
/// pub struct DummyStructure;
///
/// impl Plugin for DummyStructure {
/// fn init(&self, host: &dyn EwwiiAPI) {
/// host.widget_reg_action(Box::new(|wrg| {
/// // wrg = widget_backend::WidgetRegistryRepr
/// // The gtk4::Widget can be modified here.
/// }));
/// }
/// }
/// ```
#[cfg(feature = "include-gtk4")]
fn widget_reg_action(
&self,
f: Box<dyn FnOnce(&mut widget_backend::WidgetRegistryRepr) + Send>,
) -> Result<(), String>;
}
/// The API format that the plugin should follow.
/// This trait should be implemented for a structure and
/// that structure should be exported via FFI.
///
/// ## Example
///
/// ```rust
/// use ewwii_plugin_api::{Plugin, EwwiiAPI, export_plugin};
///
/// struct MyStruct;
///
/// impl Plugin for MyStruct {
/// fn init(&self, host: &dyn EwwiiAPI) {
/// /* Implementation Skipped */
/// }
/// }
///
/// // Automatically does all the FFI related exports
/// export_plugin!(MyStruct);
/// ```
pub trait Plugin: Send + Sync {
/// Function ran by host to startup plugin (and its a must-have for plugin loading)
fn init(&self, host: &dyn EwwiiAPI);
}

View 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::*;

View File

@@ -0,0 +1,21 @@
//! Module exposing structures and types from the
//! Widget rendering and definition backend in ewwii.
#[cfg(feature = "include-gtk4")]
mod gtk4_included {
use gtk4::Widget as GtkWidget;
use std::collections::HashMap;
/// _(include-gtk4)_ A representation of widget registry which holds all the
/// information needed for the dynamic runtime engine in ewwii.
///
/// Not every change in this structure will be represented in the
/// original WidgetRegistry in ewwii. Only the change on gtk4::Widget
/// is reflected back.
pub struct WidgetRegistryRepr<'a> {
pub widgets: HashMap<u64, &'a mut GtkWidget>,
}
}
#[cfg(feature = "include-gtk4")]
pub use gtk4_included::*;

View File

@@ -10,10 +10,10 @@ homepage = "https://github.com/byson94/ewwii"
[dependencies]
shared_utils.workspace = true
scan_prop_proc.workspace = true
rhai = { workspace = true, features = ["internals"] }
anyhow.workspace = true
gtk.workspace = true
tokio = { workspace = true, features = ["full"] }
log.workspace = true
once_cell.workspace = true
@@ -22,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

View File

@@ -1,20 +1,22 @@
use ahash::AHasher;
use anyhow::Result;
use rhai::Map;
use scan_prop_proc::scan_prop;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
#[derive(Debug, Clone)]
#[scan_prop]
pub enum WidgetNode {
Label { props: Map },
Box { props: Map, children: Vec<WidgetNode> },
CenterBox { props: Map, children: Vec<WidgetNode> },
FlowBox { props: Map, children: Vec<WidgetNode> },
Button { props: Map },
Image { props: Map },
Input { props: Map },
Progress { props: Map },
ComboBoxText { props: Map },
Slider { props: Map },
Scale { props: Map },
Checkbox { props: Map },
Expander { props: Map, children: Vec<WidgetNode> },
Revealer { props: Map, children: Vec<WidgetNode> },
@@ -30,6 +32,11 @@ pub enum WidgetNode {
EventBox { props: Map, children: Vec<WidgetNode> },
ToolTip { props: Map, children: Vec<WidgetNode> },
// Special
LocalBind { props: Map, children: Vec<WidgetNode> },
WidgetAction { props: Map, children: Vec<WidgetNode> },
GtkUI { props: Map },
// Top-level macros
DefWindow { name: String, props: Map, node: Box<WidgetNode> },
// Poll { var: String, interval: String, cmd: String, initial: String },
@@ -72,16 +79,9 @@ pub fn get_id_to_widget_info<'a>(
get_id_to_widget_info(child, id_to_props, Some(id))?;
}
}
WidgetNode::CenterBox { props, children } => {
let id = hash_props_and_type(props, "CenterBox");
insert_wdgt_info(
node,
props,
"CenterBox",
children.as_slice(),
parent_id,
id_to_props,
)?;
WidgetNode::FlowBox { props, children } => {
let id = hash_props_and_type(props, "FlowBox");
insert_wdgt_info(node, props, "FlowBox", children.as_slice(), parent_id, id_to_props)?;
for child in children {
get_id_to_widget_info(child, id_to_props, Some(id))?;
}
@@ -105,9 +105,9 @@ pub fn get_id_to_widget_info<'a>(
// let id = hash_props_and_type(props, "Transform");
insert_wdgt_info(node, props, "Transform", &[], parent_id, id_to_props)?;
}
WidgetNode::Slider { props } => {
// let id = hash_props_and_type(props, "Slider");
insert_wdgt_info(node, props, "Slider", &[], parent_id, id_to_props)?;
WidgetNode::Scale { props } => {
// let id = hash_props_and_type(props, "Scale");
insert_wdgt_info(node, props, "Scale", &[], parent_id, id_to_props)?;
}
WidgetNode::Progress { props } => {
// let id = hash_props_and_type(props, "Progress");
@@ -151,6 +151,37 @@ pub fn get_id_to_widget_info<'a>(
get_id_to_widget_info(child, id_to_props, Some(id))?;
}
}
WidgetNode::LocalBind { props, children } => {
let id = hash_props_and_type(props, "LocalBind");
insert_wdgt_info(
node,
props,
"LocalBind",
children.as_slice(),
parent_id,
id_to_props,
)?;
for child in children {
get_id_to_widget_info(child, id_to_props, Some(id))?;
}
}
WidgetNode::WidgetAction { props, children } => {
let id = hash_props_and_type(props, "WidgetAction");
insert_wdgt_info(
node,
props,
"WidgetAction",
children.as_slice(),
parent_id,
id_to_props,
)?;
for child in children {
get_id_to_widget_info(child, id_to_props, Some(id))?;
}
}
WidgetNode::GtkUI { props } => {
insert_wdgt_info(node, props, "GtkUI", &[], parent_id, id_to_props)?;
}
WidgetNode::ColorChooser { props } => {
// let id = hash_props_and_type(props, "ColorChooser");
insert_wdgt_info(node, props, "ColorChooser", &[], parent_id, id_to_props)?;
@@ -227,6 +258,14 @@ pub fn hash_props_and_type(props: &Map, widget_type_str: &str) -> u64 {
hasher.finish()
}
pub fn hash_props(props: &Map) -> u64 {
let mut hasher = AHasher::default();
props.hash(&mut hasher);
hasher.finish()
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -1,7 +1,9 @@
use crate::ast::WidgetNode;
use crate::ast::{hash_props, WidgetNode};
use crate::updates::{register_signal, LocalDataBinder, LocalSignal};
use rhai::{Array, Engine, EvalAltResult, Map, NativeCallContext};
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
/// Converts a Dynamic array into a Vec<WidgetNode>, returning proper errors with position.
fn children_to_vec(
@@ -22,8 +24,13 @@ fn children_to_vec(
.collect()
}
pub fn register_all_widgets(engine: &mut Engine, all_nodes: &Rc<RefCell<Vec<WidgetNode>>>) {
pub fn register_all_widgets(
engine: &mut Engine,
all_nodes: &Rc<RefCell<Vec<WidgetNode>>>,
keep_signal: &Rc<RefCell<Vec<u64>>>,
) {
engine.register_type::<WidgetNode>();
engine.register_type::<LocalSignal>();
// == Primitive widgets ==
macro_rules! register_primitive {
@@ -40,7 +47,7 @@ pub fn register_all_widgets(engine: &mut Engine, all_nodes: &Rc<RefCell<Vec<Widg
register_primitive!("input", Input);
register_primitive!("progress", Progress);
register_primitive!("combo_box_text", ComboBoxText);
register_primitive!("slider", Slider);
register_primitive!("scale", Scale);
register_primitive!("checkbox", Checkbox);
register_primitive!("calendar", Calendar);
register_primitive!("graph", Graph);
@@ -66,7 +73,7 @@ pub fn register_all_widgets(engine: &mut Engine, all_nodes: &Rc<RefCell<Vec<Widg
}
register_with_children!("box", Box);
register_with_children!("centerbox", CenterBox);
register_with_children!("flowbox", FlowBox);
register_with_children!("expander", Expander);
register_with_children!("revealer", Revealer);
register_with_children!("scroll", Scroll);
@@ -74,6 +81,35 @@ pub fn register_all_widgets(engine: &mut Engine, all_nodes: &Rc<RefCell<Vec<Widg
register_with_children!("stack", Stack);
register_with_children!("eventbox", EventBox);
register_with_children!("tooltip", ToolTip);
register_with_children!("localbind", LocalBind);
register_with_children!("widget_action", WidgetAction);
// == Special widget
engine.register_fn(
"gtk_ui",
|path: &str, load: &str| -> Result<WidgetNode, Box<EvalAltResult>> {
let mut props = Map::new();
props.insert("file".into(), path.into());
props.insert("id".into(), load.into());
Ok(WidgetNode::GtkUI { props })
},
);
// == Special signal
let keep_signal_clone = keep_signal.clone();
engine.register_fn(
"localsignal",
move |props: Map| -> Result<LocalSignal, Box<EvalAltResult>> {
let id = hash_props(&props);
let signal = Rc::new(LocalSignal { id, props, data: Arc::new(LocalDataBinder::new()) });
let signal_rc = register_signal(id, signal);
keep_signal_clone.borrow_mut().push(id);
Ok((*signal_rc).clone())
},
);
// == Top-level macros ==
engine.register_fn(

View File

@@ -39,9 +39,9 @@ impl WidgetNode {
props: with_dyn_id(props.clone(), parent_path),
children: process_children(children, parent_path, "box"),
},
WidgetNode::CenterBox { props, children } => WidgetNode::CenterBox {
WidgetNode::FlowBox { props, children } => WidgetNode::FlowBox {
props: with_dyn_id(props.clone(), parent_path),
children: process_children(children, parent_path, "centerbox"),
children: process_children(children, parent_path, "flowbox"),
},
WidgetNode::Expander { props, children } => WidgetNode::Expander {
props: with_dyn_id(props.clone(), parent_path),
@@ -71,6 +71,14 @@ impl WidgetNode {
props: with_dyn_id(props.clone(), parent_path),
children: process_children(children, parent_path, "tooltip"),
},
WidgetNode::LocalBind { props, children } => WidgetNode::LocalBind {
props: with_dyn_id(props.clone(), parent_path),
children: process_children(children, parent_path, "localbind"),
},
WidgetNode::WidgetAction { props, children } => WidgetNode::WidgetAction {
props: with_dyn_id(props.clone(), parent_path),
children: process_children(children, parent_path, "widget_action"),
},
// == Top-level container for multiple widgets ==
WidgetNode::Enter(children) => {
@@ -94,13 +102,14 @@ impl WidgetNode {
| node @ WidgetNode::Input { props }
| node @ WidgetNode::Progress { props }
| node @ WidgetNode::ComboBoxText { props }
| node @ WidgetNode::Slider { props }
| node @ WidgetNode::Scale { props }
| node @ WidgetNode::Checkbox { props }
| node @ WidgetNode::Calendar { props }
| node @ WidgetNode::ColorButton { props }
| node @ WidgetNode::ColorChooser { props }
| node @ WidgetNode::CircularProgress { props }
| node @ WidgetNode::Graph { props }
| node @ WidgetNode::GtkUI { props }
| node @ WidgetNode::Transform { props } => {
let new_props = with_dyn_id(props.clone(), parent_path);
match node {
@@ -112,7 +121,7 @@ impl WidgetNode {
WidgetNode::ComboBoxText { .. } => {
WidgetNode::ComboBoxText { props: new_props }
}
WidgetNode::Slider { .. } => WidgetNode::Slider { props: new_props },
WidgetNode::Scale { .. } => WidgetNode::Scale { props: new_props },
WidgetNode::Checkbox { .. } => WidgetNode::Checkbox { props: new_props },
WidgetNode::Calendar { .. } => WidgetNode::Calendar { props: new_props },
WidgetNode::ColorButton { .. } => WidgetNode::ColorButton { props: new_props },
@@ -123,6 +132,7 @@ impl WidgetNode {
WidgetNode::CircularProgress { props: new_props }
}
WidgetNode::Graph { .. } => WidgetNode::Graph { props: new_props },
WidgetNode::GtkUI { .. } => WidgetNode::GtkUI { props: new_props },
WidgetNode::Transform { .. } => WidgetNode::Transform { props: new_props },
_ => unreachable!(),
}

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,16 @@
use crate::error::{format_eval_error, format_parse_error};
use crate::parser::ParseConfig;
use rhai::{Engine, EvalAltResult, Module, ModuleResolver, Position, AST};
use crate::updates::ReactiveVarStore;
use rhai::Scope;
use rhai::{Dynamic, Engine, EvalAltResult, Module, ModuleResolver, Position, AST};
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
use std::rc::Rc;
pub struct SimpleFileResolver;
pub struct SimpleFileResolver {
pub pl_handler_store: Option<ReactiveVarStore>,
}
impl ModuleResolver for SimpleFileResolver {
fn resolve(
@@ -50,12 +55,41 @@ impl ModuleResolver for SimpleFileResolver {
format_parse_error(&e, &script, full_path.to_str()).into(),
))
})?;
let 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) => {
let name_to_val: &HashMap<String, String> = &*val.read().unwrap();
for (name, val) in name_to_val {
scope.set_value(name.clone(), Dynamic::from(val.clone()));
}
}
None => {}
}
let mut module = Module::eval_ast_as_new(scope, &ast, engine).map_err(|e| {
Box::new(EvalAltResult::ErrorSystem(
"module_eval_failed".into(),

View File

@@ -5,37 +5,45 @@ use crate::{
helper::extract_poll_and_listen_vars,
module_resolver::SimpleFileResolver,
providers::register_all_providers,
updates::ReactiveVarStore,
};
use anyhow::{anyhow, Result};
use rhai::{Dynamic, Engine, 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() -> Self {
pub fn new(pl_handler_store: Option<ReactiveVarStore>) -> Self {
let mut engine = Engine::new();
let all_nodes = Rc::new(RefCell::new(Vec::new()));
let keep_signal = Rc::new(RefCell::new(Vec::new()));
engine.set_max_expr_depths(128, 128);
engine.set_module_resolver(SimpleFileResolver);
engine
.set_module_resolver(SimpleFileResolver { pl_handler_store: pl_handler_store.clone() });
register_all_widgets(&mut engine, &all_nodes);
register_all_providers(&mut engine);
register_all_widgets(&mut engine, &all_nodes, &keep_signal);
register_all_providers(&mut engine, pl_handler_store);
Self { engine, all_nodes }
Self { engine, all_nodes, keep_signal }
}
pub fn 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(
@@ -63,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();
@@ -84,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))?)
@@ -146,4 +175,11 @@ impl ParseConfig {
pub fn set_opt_level(&mut self, opt_lvl: OptimizationLevel) {
self.engine.set_optimization_level(opt_lvl);
}
pub fn action_with_engine<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut Engine) -> R,
{
f(&mut self.engine)
}
}

View File

@@ -11,19 +11,23 @@ mod apilib;
mod stdlib;
use crate::module_resolver::{ChainedResolver, SimpleFileResolver};
use crate::updates::ReactiveVarStore;
use rhai::module_resolvers::StaticModuleResolver;
// expose the api's publically
pub use apilib::register_apilib;
pub use stdlib::register_stdlib;
pub fn register_all_providers(engine: &mut rhai::Engine) {
pub fn register_all_providers(engine: &mut rhai::Engine, plhs: Option<ReactiveVarStore>) {
let mut resolver = StaticModuleResolver::new();
// modules
register_stdlib(&mut resolver);
register_apilib(&mut resolver);
let chained = ChainedResolver { first: SimpleFileResolver, second: resolver.clone() };
let chained = ChainedResolver {
first: SimpleFileResolver { pl_handler_store: plhs.clone() },
second: resolver.clone(),
};
engine.set_module_resolver(chained);
}

View File

@@ -7,9 +7,7 @@ use rhai::exported_module;
use rhai::module_resolvers::StaticModuleResolver;
pub fn register_stdlib(resolver: &mut StaticModuleResolver) {
use crate::providers::stdlib::{
command::command, env::env, regex::regex_lib, text::text,
};
use crate::providers::stdlib::{command::command, env::env, regex::regex_lib, text::text};
// adding modules
let text_mod = exported_module!(text);

View File

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

View 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(&current);
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(&current);
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(&current);
} 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);
}
}
});
}

View File

@@ -15,12 +15,16 @@
*/
mod listen;
mod localsignal;
mod poll;
pub use localsignal::*;
use crate::ast::WidgetNode;
use listen::handle_listen;
use once_cell::sync::Lazy;
use poll::handle_poll;
use std::process::Command;
use std::sync::Mutex;
use std::{collections::HashMap, sync::Arc, sync::RwLock};
use tokio::sync::mpsc::UnboundedSender;
@@ -30,22 +34,30 @@ pub type ReactiveVarStore = Arc<RwLock<HashMap<String, String>>>;
pub static SHUTDOWN_REGISTRY: Lazy<Mutex<Vec<watch::Sender<bool>>>> =
Lazy::new(|| Mutex::new(Vec::new()));
pub fn get_prefered_shell() -> String {
// Check Dash and prefer if dash is installed.
let dash_installed: bool =
Command::new("which").arg("dash").output().map(|o| o.status.success()).unwrap_or(false);
let shell = if dash_installed { String::from("/bin/dash") } else { String::from("/bin/sh") };
shell
}
pub fn handle_state_changes(
root_node: &WidgetNode,
tx: UnboundedSender<String>,
) -> ReactiveVarStore {
// Enter node is the WidgetNode of Enter()
// it is the very root of every config.
let store: ReactiveVarStore = Arc::new(RwLock::new(HashMap::new()));
store: ReactiveVarStore,
) {
let shell = get_prefered_shell();
if let WidgetNode::Enter(children) = root_node {
for child in children {
match child {
WidgetNode::Poll { var, props } => {
handle_poll(var.to_string(), props, store.clone(), tx.clone());
handle_poll(var.to_string(), props, shell.clone(), store.clone(), tx.clone());
}
WidgetNode::Listen { var, props } => {
handle_listen(var.to_string(), props, store.clone(), tx.clone());
handle_listen(var.to_string(), props, shell.clone(), store.clone(), tx.clone());
}
_ => {}
}
@@ -53,8 +65,6 @@ pub fn handle_state_changes(
} else {
log::warn!("Expected Enter() as root node for config");
}
store
}
pub fn kill_state_change_handler() {

View File

@@ -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>,
) {
@@ -41,6 +42,14 @@ pub fn handle_poll(
}
};
// Skip unchanged values?
const DEFAULT_SKIP: bool = true;
let skip_unchanged =
get_bool_prop(props, "skip_unchanged", Some(DEFAULT_SKIP)).unwrap_or_else(|e| {
log::warn!("Failed to parse skip_unchanged property of poll {}: {}", var_name, e);
DEFAULT_SKIP
});
// No need to do this as we apply the initial value before parsing
// Handle initial value
// if let Ok(initial) = get_string_prop(&props, "initial", None) {
@@ -57,7 +66,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()
@@ -95,12 +104,18 @@ pub fn handle_poll(
if let Ok(Some(stdout_line)) = output_line {
let stdout_trimmed = stdout_line.trim().to_string();
if Some(&stdout_trimmed) != last_value.as_ref() {
// changed → always send
last_value = Some(stdout_trimmed.clone());
log::debug!("[{}] polled value: {}", var_name, stdout_trimmed);
store.write().unwrap().insert(var_name.clone(), stdout_trimmed);
let _ = tx.send(var_name.clone());
} else {
} else if skip_unchanged {
// unchanged + skipping enabled
log::trace!("[{}] value unchanged, skipping tx", var_name);
} else {
// unchanged + skipping disabled → still send
log::trace!("[{}] value unchanged, skipping disabled", var_name);
let _ = tx.send(var_name.clone());
}
} else {
log::warn!("[{}] shell output ended or failed: {:?}", var_name, output_line);

View File

@@ -4,11 +4,12 @@ version = "0.1.0"
authors = ["byson94 <byson94wastaken@gmail.com>"]
edition = "2021"
license = "GPL-3.0-or-later"
description = "Utility crate used in eww"
repository = "https://github.com/elkowar/eww"
homepage = "https://github.com/elkowar/eww"
description = "Utility crate used in ewwii"
repository = "https://github.com/ewwii-sh/ewwii"
homepage = "https://github.com/ewwii-sh/ewwii"
[dependencies]
serde.workspace = true
rhai.workspace = true
anyhow.workspace = true
rhai = { workspace = true, features = ["internals"] }
anyhow.workspace = true
once_cell.workspace = true

View 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"})
),
]);

View File

@@ -0,0 +1,9 @@
* {
all: unset;
}
.activate {
background: transparent;
color: gray;
}

View File

@@ -1,5 +1,5 @@
fn bar(music_var, volume, time) {
return centerbox(#{ orientation: "h" }, [
return box(#{ orientation: "h" }, [
workspaces(),
music(music_var),
sidestuff(volume, time),
@@ -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 != "",

View 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

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

View File

@@ -5,5 +5,5 @@ edition = "2024"
[dependencies]
rhai_impl.workspace = true
rhai = "1.22.2"
rhai.workspace = true
rhai-autodocs = "0.9.0"