graphix_package_gui/
lib.rs1#![doc(
2 html_logo_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg",
3 html_favicon_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg"
4)]
5use async_trait::async_trait;
6use graphix_compiler::{
7 env::Env,
8 expr::{ExprId, ModPath},
9 typ::{Type, TypeRef},
10};
11use graphix_package::CustomDisplay;
12use graphix_rt::{CompExp, GXExt, GXHandle};
13use log::error;
14use netidx::publisher::Value;
15use std::{marker::PhantomData, sync::LazyLock};
16use tokio::sync::oneshot;
17use triomphe::Arc;
18use winit::{event_loop::EventLoopProxy, window::WindowId};
19
20mod clipboard;
21pub mod convert;
22mod event_loop;
23pub mod render;
24pub mod theme;
25pub mod types;
26pub mod widgets;
27pub mod window;
28
29#[cfg(test)]
30mod test;
31
32pub(crate) enum ToGui {
33 Update(ExprId, Value),
34 ResizeTimer(WindowId),
41 ResizeEnd(WindowId, crate::types::SizeV),
49 Redraw,
54 Stop(oneshot::Sender<()>),
55}
56
57#[derive(Clone)]
63pub(crate) struct RedrawWaker {
64 proxy: EventLoopProxy<ToGui>,
65}
66
67impl RedrawWaker {
68 pub(crate) fn new(proxy: EventLoopProxy<ToGui>) -> Self {
69 Self { proxy }
70 }
71
72 pub(crate) fn wake(&self) {
73 let _ = self.proxy.send_event(ToGui::Redraw);
74 }
75}
76
77pub(crate) static REDRAW_WAKER: std::sync::OnceLock<RedrawWaker> =
81 std::sync::OnceLock::new();
82
83struct Gui<X: GXExt> {
84 proxy: EventLoopProxy<ToGui>,
85 ph: PhantomData<X>,
86}
87
88impl<X: GXExt> Gui<X> {
89 async fn start(
90 gx: &GXHandle<X>,
91 _env: Env,
92 root: CompExp<X>,
93 stop: oneshot::Sender<()>,
94 run_on_main: graphix_package::MainThreadHandle,
95 ) -> Self {
96 let gx = gx.clone();
97 let (proxy_tx, proxy_rx) = oneshot::channel();
98 let rt_handle = tokio::runtime::Handle::current();
99 run_on_main
100 .run(Box::new(move || {
101 event_loop::run(gx, root, proxy_tx, stop, rt_handle);
102 }))
103 .expect("main thread receiver dropped");
104 let proxy = proxy_rx.await.expect("event loop failed to send proxy");
105 Self { proxy, ph: PhantomData }
106 }
107
108 fn update(&self, id: ExprId, v: Value) {
109 if self.proxy.send_event(ToGui::Update(id, v)).is_err() {
110 error!("could not send update because gui event loop closed")
111 }
112 }
113}
114
115pub static GUITYP: LazyLock<Type> = LazyLock::new(|| {
116 Type::Array(Arc::new(Type::ByRef(Arc::new(Type::Ref (TypeRef {
117 scope: ModPath::root(),
118 name: ModPath::from(["gui", "Window"]),
119 params: Arc::from_iter([]),
120 ..Default::default()})))))
121});
122
123#[async_trait]
124impl<X: GXExt> CustomDisplay<X> for Gui<X> {
125 async fn clear(&mut self) {
126 let (tx, rx) = oneshot::channel::<()>();
127 let _ = self.proxy.send_event(ToGui::Stop(tx));
128 let _ = rx.await;
129 }
130
131 async fn process_update(&mut self, _env: &Env, id: ExprId, v: Value) {
132 self.update(id, v);
133 }
134}
135
136graphix_derive::defpackage! {
137 builtins => [
138 clipboard::ReadText,
139 clipboard::WriteText,
140 clipboard::ReadImage,
141 clipboard::WriteImage,
142 clipboard::ReadHtml,
143 clipboard::WriteHtml,
144 clipboard::ReadFiles,
145 clipboard::WriteFiles,
146 clipboard::Clear,
147 ],
148 is_custom => |gx, env, e| {
149 if let Some(typ) = e.typ.with_deref(|t| t.cloned())
150 && typ != Type::Bottom
151 && typ != Type::Any
152 {
153 GUITYP.contains(env, &typ).unwrap_or(false)
154 } else {
155 false
156 }
157 },
158 init_custom => |gx, env, stop, e, run_on_main| {
159 Ok(Box::new(Gui::<X>::start(gx, env.clone(), e, stop, run_on_main).await))
160 },
161}